From e87b5bce85b043023495de9febee7620476d6470 Mon Sep 17 00:00:00 2001 From: zapadi Date: Wed, 12 Mar 2025 22:30:51 +0200 Subject: [PATCH 01/14] [Equality] Improvements --- .../Internals/HashCodeHelper.cs | 25 ++++++++++++ src/redmine-net-api/Types/Attachment.cs | 12 +++--- src/redmine-net-api/Types/ChangeSet.cs | 2 +- src/redmine-net-api/Types/CustomField.cs | 33 ++++++++-------- .../Types/CustomFieldPossibleValue.cs | 16 +++----- src/redmine-net-api/Types/CustomFieldValue.cs | 2 +- src/redmine-net-api/Types/Detail.cs | 8 ++-- src/redmine-net-api/Types/DocumentCategory.cs | 3 +- src/redmine-net-api/Types/Error.cs | 2 +- src/redmine-net-api/Types/File.cs | 12 +++--- src/redmine-net-api/Types/Group.cs | 6 +-- src/redmine-net-api/Types/Identifiable.cs | 4 +- src/redmine-net-api/Types/IdentifiableName.cs | 2 +- src/redmine-net-api/Types/Issue.cs | 24 ++++++------ src/redmine-net-api/Types/IssueChild.cs | 3 +- src/redmine-net-api/Types/IssueCustomField.cs | 5 +-- src/redmine-net-api/Types/IssuePriority.cs | 2 +- src/redmine-net-api/Types/IssueRelation.cs | 6 ++- src/redmine-net-api/Types/IssueStatus.cs | 4 +- src/redmine-net-api/Types/Journal.cs | 19 +++++----- src/redmine-net-api/Types/Membership.cs | 2 +- src/redmine-net-api/Types/MyAccount.cs | 17 +++++---- .../Types/MyAccountCustomField.cs | 9 ++--- src/redmine-net-api/Types/News.cs | 29 ++++++++------ src/redmine-net-api/Types/NewsComment.cs | 18 ++++++--- src/redmine-net-api/Types/Permission.cs | 2 +- src/redmine-net-api/Types/Project.cs | 24 ++++++------ .../Types/ProjectMembership.cs | 8 ++-- src/redmine-net-api/Types/Role.cs | 14 +++---- src/redmine-net-api/Types/Search.cs | 8 ++-- src/redmine-net-api/Types/TimeEntry.cs | 4 +- .../Types/TimeEntryActivity.cs | 4 +- src/redmine-net-api/Types/Tracker.cs | 5 ++- src/redmine-net-api/Types/TrackerCoreField.cs | 13 ++++++- src/redmine-net-api/Types/Upload.cs | 8 ++-- src/redmine-net-api/Types/User.cs | 38 +++++++++---------- src/redmine-net-api/Types/Version.cs | 18 ++++----- src/redmine-net-api/Types/WikiPage.cs | 9 +++-- 38 files changed, 236 insertions(+), 184 deletions(-) diff --git a/src/redmine-net-api/Internals/HashCodeHelper.cs b/src/redmine-net-api/Internals/HashCodeHelper.cs index f2610116..4d2ac468 100755 --- a/src/redmine-net-api/Internals/HashCodeHelper.cs +++ b/src/redmine-net-api/Internals/HashCodeHelper.cs @@ -57,6 +57,31 @@ public static int GetHashCode(IList list, int hash) where T : class return hashCode; } } + + public static int GetHashCode(List list, int hash) where T : class + { + unchecked + { + var hashCode = hash; + if (list == null) + { + return hashCode; + } + + hashCode = (hashCode * 17) + list.Count; + + foreach (var t in list) + { + hashCode *= 17; + if (t != null) + { + hashCode += t.GetHashCode(); + } + } + + return hashCode; + } + } /// /// Returns a hash code for this instance. diff --git a/src/redmine-net-api/Types/Attachment.cs b/src/redmine-net-api/Types/Attachment.cs index bc394f77..da35047f 100644 --- a/src/redmine-net-api/Types/Attachment.cs +++ b/src/redmine-net-api/Types/Attachment.cs @@ -189,12 +189,12 @@ public override bool Equals(Attachment other) { if (other == null) return false; return base.Equals(other) - && string.Equals(FileName, other.FileName, StringComparison.OrdinalIgnoreCase) - && string.Equals(ContentType, other.ContentType, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(ContentUrl, other.ContentUrl, StringComparison.OrdinalIgnoreCase) - && string.Equals(ThumbnailUrl, other.ThumbnailUrl, StringComparison.OrdinalIgnoreCase) - && Equals(Author, other.Author) + && string.Equals(FileName, other.FileName, StringComparison.Ordinal) + && string.Equals(ContentType, other.ContentType, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(ContentUrl, other.ContentUrl, StringComparison.Ordinal) + && string.Equals(ThumbnailUrl, other.ThumbnailUrl, StringComparison.Ordinal) + && Author == other.Author && FileSize == other.FileSize && CreatedOn == other.CreatedOn; } diff --git a/src/redmine-net-api/Types/ChangeSet.cs b/src/redmine-net-api/Types/ChangeSet.cs index dae77667..3e9937af 100644 --- a/src/redmine-net-api/Types/ChangeSet.cs +++ b/src/redmine-net-api/Types/ChangeSet.cs @@ -154,7 +154,7 @@ public bool Equals(ChangeSet other) return Revision == other.Revision && User == other.User - && Comments == other.Comments + && string.Equals(Comments, other.Comments, StringComparison.Ordinal) && CommittedOn == other.CommittedOn; } diff --git a/src/redmine-net-api/Types/CustomField.cs b/src/redmine-net-api/Types/CustomField.cs index 486e492f..f10a4162 100644 --- a/src/redmine-net-api/Types/CustomField.cs +++ b/src/redmine-net-api/Types/CustomField.cs @@ -207,22 +207,23 @@ public bool Equals(CustomField other) { if (other == null) return false; - return base.Equals(other) - && string.Equals(CustomizedType, other.CustomizedType, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(FieldFormat, other.FieldFormat, StringComparison.OrdinalIgnoreCase) - && string.Equals(Regexp, other.Regexp, StringComparison.OrdinalIgnoreCase) - && string.Equals(DefaultValue, other.DefaultValue, StringComparison.Ordinal) - && MinLength == other.MinLength - && MaxLength == other.MaxLength - && IsRequired == other.IsRequired - && IsFilter == other.IsFilter - && Searchable == other.Searchable - && Multiple == other.Multiple - && Visible == other.Visible - && Equals(PossibleValues, other.PossibleValues) - && Equals(Trackers, other.Trackers) - && Equals(Roles, other.Roles); + var result = base.Equals(other) + && string.Equals(CustomizedType, other.CustomizedType, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(FieldFormat, other.FieldFormat, StringComparison.Ordinal) + && string.Equals(Regexp, other.Regexp, StringComparison.Ordinal) + && string.Equals(DefaultValue, other.DefaultValue, StringComparison.Ordinal) + && MinLength == other.MinLength + && MaxLength == other.MaxLength + && IsRequired == other.IsRequired + && IsFilter == other.IsFilter + && Searchable == other.Searchable + && Multiple == other.Multiple + && Visible == other.Visible + && (PossibleValues?.Equals(other.PossibleValues) ?? other.PossibleValues == null) + && (Trackers?.Equals(other.Trackers) ?? other.Trackers == null) + && (Roles?.Equals(other.Roles) ?? other.Roles == null); + return result; } /// diff --git a/src/redmine-net-api/Types/CustomFieldPossibleValue.cs b/src/redmine-net-api/Types/CustomFieldPossibleValue.cs index 8c39c325..2d3d0029 100644 --- a/src/redmine-net-api/Types/CustomFieldPossibleValue.cs +++ b/src/redmine-net-api/Types/CustomFieldPossibleValue.cs @@ -73,9 +73,7 @@ public void ReadXml(XmlReader reader) switch (reader.Name) { case RedmineKeys.LABEL: Label = reader.ReadElementContentAsString(); break; - case RedmineKeys.VALUE: Value = reader.ReadElementContentAsString(); break; - default: reader.Read(); break; } } @@ -111,14 +109,9 @@ public void ReadJson(JsonReader reader) switch (reader.Value) { - case RedmineKeys.LABEL: - Label = reader.ReadAsString(); break; - - case RedmineKeys.VALUE: - - Value = reader.ReadAsString(); break; - default: - reader.Read(); break; + case RedmineKeys.LABEL: Label = reader.ReadAsString(); break; + case RedmineKeys.VALUE: Value = reader.ReadAsString(); break; + default: reader.Read(); break; } } } @@ -139,8 +132,9 @@ public void WriteJson(JsonWriter writer) { } public bool Equals(CustomFieldPossibleValue other) { if (other == null) return false; - return string.Equals(Value, other.Value, StringComparison.Ordinal) + var result = string.Equals(Value, other.Value, StringComparison.Ordinal) && string.Equals(Label, other.Label, StringComparison.Ordinal); + return result; } /// diff --git a/src/redmine-net-api/Types/CustomFieldValue.cs b/src/redmine-net-api/Types/CustomFieldValue.cs index 592bb4f4..19569f1e 100644 --- a/src/redmine-net-api/Types/CustomFieldValue.cs +++ b/src/redmine-net-api/Types/CustomFieldValue.cs @@ -146,7 +146,7 @@ public void WriteJson(JsonWriter writer) public bool Equals(CustomFieldValue other) { if (other == null) return false; - return string.Equals(Info, other.Info, StringComparison.OrdinalIgnoreCase); + return string.Equals(Info, other.Info, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/Detail.cs b/src/redmine-net-api/Types/Detail.cs index a17a0d9a..53a0b9a1 100644 --- a/src/redmine-net-api/Types/Detail.cs +++ b/src/redmine-net-api/Types/Detail.cs @@ -177,10 +177,10 @@ public void ReadJson(JsonReader reader) public bool Equals(Detail other) { if (other == null) return false; - return string.Equals(Property, other.Property, StringComparison.OrdinalIgnoreCase) - && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase) - && string.Equals(OldValue, other.OldValue, StringComparison.OrdinalIgnoreCase) - && string.Equals(NewValue, other.NewValue, StringComparison.OrdinalIgnoreCase); + return string.Equals(Property, other.Property, StringComparison.Ordinal) + && string.Equals(Name, other.Name, StringComparison.Ordinal) + && string.Equals(OldValue, other.OldValue, StringComparison.Ordinal) + && string.Equals(NewValue, other.NewValue, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/DocumentCategory.cs b/src/redmine-net-api/Types/DocumentCategory.cs index 1028a41d..4040fade 100644 --- a/src/redmine-net-api/Types/DocumentCategory.cs +++ b/src/redmine-net-api/Types/DocumentCategory.cs @@ -132,8 +132,7 @@ public bool Equals(DocumentCategory other) { if (other == null) return false; - return Id == other.Id - && Name == other.Name + return base.Equals(other) && IsDefault == other.IsDefault && IsActive == other.IsActive; } diff --git a/src/redmine-net-api/Types/Error.cs b/src/redmine-net-api/Types/Error.cs index 96cd20bd..a3712f4b 100644 --- a/src/redmine-net-api/Types/Error.cs +++ b/src/redmine-net-api/Types/Error.cs @@ -119,7 +119,7 @@ public bool Equals(Error other) { if (other == null) return false; - return string.Equals(Info,other.Info, StringComparison.OrdinalIgnoreCase); + return string.Equals(Info, other.Info, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/File.cs b/src/redmine-net-api/Types/File.cs index 4630f5f8..eb936dd1 100644 --- a/src/redmine-net-api/Types/File.cs +++ b/src/redmine-net-api/Types/File.cs @@ -210,12 +210,12 @@ public override bool Equals(File other) { if (other == null) return false; return base.Equals(other) - && string.Equals(Filename, other.Filename, StringComparison.OrdinalIgnoreCase) - && string.Equals(ContentType, other.ContentType, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(ContentUrl, other.ContentUrl, StringComparison.OrdinalIgnoreCase) - && string.Equals(Digest, other.Digest, StringComparison.OrdinalIgnoreCase) - && Equals(Author, other.Author) + && string.Equals(Filename, other.Filename, StringComparison.Ordinal) + && string.Equals(ContentType, other.ContentType, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(ContentUrl, other.ContentUrl, StringComparison.Ordinal) + && string.Equals(Digest, other.Digest, StringComparison.Ordinal) + && Author == other.Author && FileSize == other.FileSize && CreatedOn == other.CreatedOn && Version == other.Version diff --git a/src/redmine-net-api/Types/Group.cs b/src/redmine-net-api/Types/Group.cs index 45001031..71e92025 100644 --- a/src/redmine-net-api/Types/Group.cs +++ b/src/redmine-net-api/Types/Group.cs @@ -165,9 +165,9 @@ public bool Equals(Group other) { if (other == null) return false; return base.Equals(other) - && Equals(Users, other.Users) - && Equals(CustomFields, other.CustomFields) - && Equals(Memberships, other.Memberships); + && Users != null ? Users.Equals(other.Users) : other.Users == null + && CustomFields != null ? CustomFields.Equals(other.CustomFields) : other.CustomFields == null + && Memberships != null ? Memberships.Equals(other.Memberships) : other.Memberships == null; } /// diff --git a/src/redmine-net-api/Types/Identifiable.cs b/src/redmine-net-api/Types/Identifiable.cs index e5123673..b0f81444 100644 --- a/src/redmine-net-api/Types/Identifiable.cs +++ b/src/redmine-net-api/Types/Identifiable.cs @@ -1,4 +1,4 @@ -/* +/* Copyright 2011 - 2023 Adrian Popescu Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ public abstract class Identifiable : IXmlSerializable, IJsonSerializable, IEq /// Gets the id. /// /// The id. - public int Id { get; protected set; } + public int Id { get; protected internal set; } #endregion #region Implementation of IXmlSerialization diff --git a/src/redmine-net-api/Types/IdentifiableName.cs b/src/redmine-net-api/Types/IdentifiableName.cs index b4717d4e..02fb5c6d 100644 --- a/src/redmine-net-api/Types/IdentifiableName.cs +++ b/src/redmine-net-api/Types/IdentifiableName.cs @@ -167,7 +167,7 @@ public override void WriteJson(JsonWriter writer) public override bool Equals(IdentifiableName other) { if (other == null) return false; - return Id == other.Id && string.Equals(Name, other.Name, StringComparison.OrdinalIgnoreCase); + return Id == other.Id && string.Equals(Name, other.Name, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/Issue.cs b/src/redmine-net-api/Types/Issue.cs index 9e97f373..7d9b488e 100644 --- a/src/redmine-net-api/Types/Issue.cs +++ b/src/redmine-net-api/Types/Issue.cs @@ -484,8 +484,8 @@ public override bool Equals(Issue other) && Priority == other.Priority && Author == other.Author && Category == other.Category - && Subject == other.Subject - && Description == other.Description + && string.Equals(Subject, other.Subject, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) && StartDate == other.StartDate && DueDate == other.DueDate && DoneRatio == other.DoneRatio @@ -495,16 +495,16 @@ public override bool Equals(Issue other) && UpdatedOn == other.UpdatedOn && AssignedTo == other.AssignedTo && FixedVersion == other.FixedVersion - && Notes == other.Notes + && string.Equals(Notes, other.Notes, StringComparison.Ordinal) && ClosedOn == other.ClosedOn && PrivateNotes == other.PrivateNotes - && Attachments.Equals(other.Attachments) - && CustomFields.Equals(other.CustomFields) - && ChangeSets.Equals(other.ChangeSets) - && Children.Equals(other.Children) - && Journals.Equals(other.Journals) - && Relations.Equals(other.Relations) - && Watchers.Equals(other.Watchers); + && (Attachments?.Equals(other.Attachments) ?? other.Attachments == null) + && (CustomFields?.Equals(other.CustomFields) ?? other.CustomFields == null) + && (ChangeSets?.Equals(other.ChangeSets) ?? other.ChangeSets == null) + && (Children?.Equals(other.Children) ?? other.Children == null) + && (Journals?.Equals(other.Journals) ?? other.Journals == null) + && (Relations?.Equals(other.Relations) ?? other.Relations == null) + && (Watchers?.Equals(other.Watchers) ?? other.Watchers == null); } /// @@ -529,19 +529,19 @@ public override int GetHashCode() var hashCode = base.GetHashCode(); hashCode = HashCodeHelper.GetHashCode(Project, hashCode); + hashCode = HashCodeHelper.GetHashCode(Tracker, hashCode); hashCode = HashCodeHelper.GetHashCode(Status, hashCode); hashCode = HashCodeHelper.GetHashCode(Priority, hashCode); hashCode = HashCodeHelper.GetHashCode(Author, hashCode); hashCode = HashCodeHelper.GetHashCode(Category, hashCode); - + hashCode = HashCodeHelper.GetHashCode(Subject, hashCode); hashCode = HashCodeHelper.GetHashCode(Description, hashCode); hashCode = HashCodeHelper.GetHashCode(StartDate, hashCode); hashCode = HashCodeHelper.GetHashCode(Project, hashCode); hashCode = HashCodeHelper.GetHashCode(DueDate, hashCode); hashCode = HashCodeHelper.GetHashCode(DoneRatio, hashCode); - hashCode = HashCodeHelper.GetHashCode(PrivateNotes, hashCode); hashCode = HashCodeHelper.GetHashCode(EstimatedHours, hashCode); hashCode = HashCodeHelper.GetHashCode(SpentHours, hashCode); diff --git a/src/redmine-net-api/Types/IssueChild.cs b/src/redmine-net-api/Types/IssueChild.cs index f92e3fd4..6418aa4f 100644 --- a/src/redmine-net-api/Types/IssueChild.cs +++ b/src/redmine-net-api/Types/IssueChild.cs @@ -115,7 +115,8 @@ public override bool Equals(IssueChild other) { if (other == null) return false; return base.Equals(other) - && Tracker == other.Tracker && Subject == other.Subject; + && Tracker == other.Tracker + && string.Equals(Subject, other.Subject, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/IssueCustomField.cs b/src/redmine-net-api/Types/IssueCustomField.cs index 31d012cc..1cb47bf4 100644 --- a/src/redmine-net-api/Types/IssueCustomField.cs +++ b/src/redmine-net-api/Types/IssueCustomField.cs @@ -214,10 +214,9 @@ public override void ReadJson(JsonReader reader) public bool Equals(IssueCustomField other) { if (other == null) return false; - return Id == other.Id - && Name == other.Name + return base.Equals(other) && Multiple == other.Multiple - && Values.Equals(other.Values); + && (Values?.Equals(other.Values) ?? other.Values == null); } /// diff --git a/src/redmine-net-api/Types/IssuePriority.cs b/src/redmine-net-api/Types/IssuePriority.cs index 305cf04f..be107464 100644 --- a/src/redmine-net-api/Types/IssuePriority.cs +++ b/src/redmine-net-api/Types/IssuePriority.cs @@ -116,7 +116,7 @@ public bool Equals(IssuePriority other) { if (other == null) return false; - return Id == other.Id && Name == other.Name + return base.Equals(other) && IsDefault == other.IsDefault && IsActive == other.IsActive; } diff --git a/src/redmine-net-api/Types/IssueRelation.cs b/src/redmine-net-api/Types/IssueRelation.cs index 16571f7e..88ae9eb2 100644 --- a/src/redmine-net-api/Types/IssueRelation.cs +++ b/src/redmine-net-api/Types/IssueRelation.cs @@ -219,7 +219,11 @@ private static IssueRelationType ReadIssueRelationType(string value) public override bool Equals(IssueRelation other) { if (other == null) return false; - return Id == other.Id && IssueId == other.IssueId && IssueToId == other.IssueToId && Type == other.Type && Delay == other.Delay; + return Id == other.Id + && IssueId == other.IssueId + && IssueToId == other.IssueToId + && Type == other.Type + && Delay == other.Delay; } /// diff --git a/src/redmine-net-api/Types/IssueStatus.cs b/src/redmine-net-api/Types/IssueStatus.cs index 9f4657c0..1e842ac0 100644 --- a/src/redmine-net-api/Types/IssueStatus.cs +++ b/src/redmine-net-api/Types/IssueStatus.cs @@ -116,7 +116,9 @@ public override void ReadJson(JsonReader reader) public bool Equals(IssueStatus other) { if (other == null) return false; - return Id == other.Id && Name == other.Name && IsClosed == other.IsClosed && IsDefault == other.IsDefault; + return base.Equals(other) + && IsClosed == other.IsClosed + && IsDefault == other.IsDefault; } /// diff --git a/src/redmine-net-api/Types/Journal.cs b/src/redmine-net-api/Types/Journal.cs index 325eedbd..75cd78a3 100644 --- a/src/redmine-net-api/Types/Journal.cs +++ b/src/redmine-net-api/Types/Journal.cs @@ -130,8 +130,6 @@ public override void WriteXml(XmlWriter writer) #endregion #region Implementation of IJsonSerialization - - /// /// /// @@ -182,14 +180,15 @@ public override void WriteJson(JsonWriter writer) public override bool Equals(Journal other) { if (other == null) return false; - return base.Equals(other) - && Equals(User, other.User) - && Equals(Details, other.Details) - && string.Equals(Notes, other.Notes, StringComparison.OrdinalIgnoreCase) - && CreatedOn == other.CreatedOn - && UpdatedOn == other.UpdatedOn - && Equals(UpdatedBy, other.UpdatedBy) - && PrivateNotes == other.PrivateNotes; + var result = base.Equals(other); + result = result && User == other.User; + result = result && UpdatedBy == other.UpdatedBy; + result = result && (Details?.Equals(other.Details) ?? other.Details == null); + result = result && string.Equals(Notes, other.Notes, StringComparison.Ordinal); + result = result && CreatedOn == other.CreatedOn; + result = result && UpdatedOn == other.UpdatedOn; + result = result && PrivateNotes == other.PrivateNotes; + return result; } /// diff --git a/src/redmine-net-api/Types/Membership.cs b/src/redmine-net-api/Types/Membership.cs index 4243030e..f9f92ff1 100644 --- a/src/redmine-net-api/Types/Membership.cs +++ b/src/redmine-net-api/Types/Membership.cs @@ -125,7 +125,7 @@ public override bool Equals(Membership other) { if (other == null) return false; return Id == other.Id - && Project != null ? Project.Equals(other.Project) : other.Project == null + && Project == other.Project && Roles != null ? Roles.Equals(other.Roles) : other.Roles == null; } diff --git a/src/redmine-net-api/Types/MyAccount.cs b/src/redmine-net-api/Types/MyAccount.cs index fc86e19a..0bcf9831 100644 --- a/src/redmine-net-api/Types/MyAccount.cs +++ b/src/redmine-net-api/Types/MyAccount.cs @@ -186,15 +186,15 @@ public override bool Equals(MyAccount other) { if (other == null) return false; return Id == other.Id - && string.Equals(Login, other.Login, StringComparison.OrdinalIgnoreCase) - && string.Equals(FirstName, other.FirstName, StringComparison.OrdinalIgnoreCase) - && string.Equals(LastName, other.LastName, StringComparison.OrdinalIgnoreCase) - && string.Equals(ApiKey, other.ApiKey, StringComparison.OrdinalIgnoreCase) - && Email.Equals(other.Email, StringComparison.OrdinalIgnoreCase) + && string.Equals(Login, other.Login, StringComparison.Ordinal) + && string.Equals(FirstName, other.FirstName, StringComparison.Ordinal) + && string.Equals(LastName, other.LastName, StringComparison.Ordinal) + && string.Equals(ApiKey, other.ApiKey, StringComparison.Ordinal) + && string.Equals(Email, other.Email, StringComparison.Ordinal) && IsAdmin == other.IsAdmin && CreatedOn == other.CreatedOn && LastLoginOn == other.LastLoginOn - && CustomFields.Equals(other.CustomFields); + && (CustomFields?.Equals(other.CustomFields) ?? other.CustomFields == null); } /// @@ -215,15 +215,16 @@ public override int GetHashCode() { unchecked { - var hashCode = base.GetHashCode(); + var hashCode = 17; + hashCode = HashCodeHelper.GetHashCode(Id, hashCode); hashCode = HashCodeHelper.GetHashCode(Login, hashCode); hashCode = HashCodeHelper.GetHashCode(FirstName, hashCode); hashCode = HashCodeHelper.GetHashCode(LastName, hashCode); + hashCode = HashCodeHelper.GetHashCode(ApiKey, hashCode); hashCode = HashCodeHelper.GetHashCode(Email, hashCode); hashCode = HashCodeHelper.GetHashCode(IsAdmin, hashCode); hashCode = HashCodeHelper.GetHashCode(CreatedOn, hashCode); hashCode = HashCodeHelper.GetHashCode(LastLoginOn, hashCode); - hashCode = HashCodeHelper.GetHashCode(ApiKey, hashCode); hashCode = HashCodeHelper.GetHashCode(CustomFields, hashCode); return hashCode; } diff --git a/src/redmine-net-api/Types/MyAccountCustomField.cs b/src/redmine-net-api/Types/MyAccountCustomField.cs index f01ab673..8350527d 100644 --- a/src/redmine-net-api/Types/MyAccountCustomField.cs +++ b/src/redmine-net-api/Types/MyAccountCustomField.cs @@ -29,14 +29,13 @@ namespace Redmine.Net.Api.Types /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [XmlRoot(RedmineKeys.CUSTOM_FIELD)] - public sealed class MyAccountCustomField : IdentifiableName + public sealed class MyAccountCustomField : IdentifiableName, IEquatable { /// /// Initializes a new instance of the class. /// /// Serialization public MyAccountCustomField() { } - /// /// @@ -117,8 +116,7 @@ public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; - if (obj.GetType() != GetType()) return false; - return Equals(obj as MyAccountCustomField); + return obj is MyAccountCustomField other && Equals(other); } /// @@ -128,8 +126,9 @@ public override bool Equals(object obj) /// public bool Equals(MyAccountCustomField other) { + if (other == null) return false; return base.Equals(other) - && string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + && string.Equals(Value, other.Value, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/News.cs b/src/redmine-net-api/Types/News.cs index 03305ab0..e4796eef 100644 --- a/src/redmine-net-api/Types/News.cs +++ b/src/redmine-net-api/Types/News.cs @@ -202,19 +202,21 @@ public override void WriteJson(JsonWriter writer) /// /// /// - public new bool Equals(News other) + public override bool Equals(News other) { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; + if(other == null) return false; - return base.Equals(other) - && Equals(Project, other.Project) - && Equals(Author, other.Author) - && string.Equals(Title, other.Title, StringComparison.Ordinal) - && string.Equals(Summary, other.Summary, StringComparison.Ordinal) - && string.Equals(Description, other.Description, StringComparison.Ordinal) - && CreatedOn.Equals(other.CreatedOn) - && Equals(Comments, other.Comments); + var result = base.Equals(other); + result = result && Project == other.Project; + result = result && Author == other.Author; + result = result && string.Equals(Title, other.Title, StringComparison.Ordinal); + result = result && string.Equals(Summary, other.Summary, StringComparison.Ordinal); + result = result && string.Equals(Description, other.Description, StringComparison.Ordinal); + result = result && CreatedOn == other.CreatedOn; + result = result && (Attachments?.Equals(other.Attachments) ?? other.Attachments == null); + result = result && (Comments?.Equals(other.Comments) ?? other.Comments == null); + result = result && (Uploads?.Equals(other.Uploads) ?? other.Uploads == null); + return result; } /// @@ -238,7 +240,8 @@ public override int GetHashCode() { unchecked { - var hashCode = base.GetHashCode(); + var hashCode = 17; + hashCode = HashCodeHelper.GetHashCode(Id, hashCode); hashCode = HashCodeHelper.GetHashCode(Project, hashCode); hashCode = HashCodeHelper.GetHashCode(Author, hashCode); hashCode = HashCodeHelper.GetHashCode(Title, hashCode); @@ -246,6 +249,8 @@ public override int GetHashCode() hashCode = HashCodeHelper.GetHashCode(Description, hashCode); hashCode = HashCodeHelper.GetHashCode(CreatedOn, hashCode); hashCode = HashCodeHelper.GetHashCode(Comments, hashCode); + hashCode = HashCodeHelper.GetHashCode(Attachments, hashCode); + hashCode = HashCodeHelper.GetHashCode(Uploads, hashCode); return hashCode; } } diff --git a/src/redmine-net-api/Types/NewsComment.cs b/src/redmine-net-api/Types/NewsComment.cs index 92057ba9..3766204f 100644 --- a/src/redmine-net-api/Types/NewsComment.cs +++ b/src/redmine-net-api/Types/NewsComment.cs @@ -14,6 +14,7 @@ You may obtain a copy of the License at limitations under the License. */ +using System; using System.Diagnostics; using System.Xml; using System.Xml.Serialization; @@ -102,7 +103,9 @@ public override void WriteJson(JsonWriter writer) public override bool Equals(NewsComment other) { if (other == null) return false; - return Id == other.Id && Author == other.Author && Content == other.Content; + return Id == other.Id + && Author == other.Author + && string.Equals(Content, other.Content, StringComparison.Ordinal); } /// @@ -121,12 +124,15 @@ public override bool Equals(object obj) /// public override int GetHashCode() { - var hashCode = base.GetHashCode(); - - hashCode = HashCodeHelper.GetHashCode(Author, hashCode); - hashCode = HashCodeHelper.GetHashCode(Content, hashCode); + unchecked + { + var hashCode = 17; + hashCode = HashCodeHelper.GetHashCode(Id, hashCode); + hashCode = HashCodeHelper.GetHashCode(Author, hashCode); + hashCode = HashCodeHelper.GetHashCode(Content, hashCode); - return hashCode; + return hashCode; + } } /// diff --git a/src/redmine-net-api/Types/Permission.cs b/src/redmine-net-api/Types/Permission.cs index d83e887f..fba871af 100644 --- a/src/redmine-net-api/Types/Permission.cs +++ b/src/redmine-net-api/Types/Permission.cs @@ -98,7 +98,7 @@ public void WriteJson(JsonWriter writer) { } /// public bool Equals(Permission other) { - return other != null && Info == other.Info; + return other != null && string.Equals(Info, other.Info, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/Project.cs b/src/redmine-net-api/Types/Project.cs index 2af4593f..55af2209 100644 --- a/src/redmine-net-api/Types/Project.cs +++ b/src/redmine-net-api/Types/Project.cs @@ -308,23 +308,23 @@ public bool Equals(Project other) } return base.Equals(other) - && string.Equals(Identifier, other.Identifier, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(HomePage, other.HomePage, StringComparison.OrdinalIgnoreCase) - && string.Equals(Identifier, other.Identifier, StringComparison.OrdinalIgnoreCase) + && string.Equals(Identifier, other.Identifier, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(HomePage, other.HomePage, StringComparison.Ordinal) + && string.Equals(Identifier, other.Identifier, StringComparison.Ordinal) && CreatedOn == other.CreatedOn && UpdatedOn == other.UpdatedOn && Status == other.Status && IsPublic == other.IsPublic && InheritMembers == other.InheritMembers - && Equals(DefaultAssignee, other.DefaultAssignee) - && Equals(DefaultVersion, other.DefaultVersion) - && Equals(Parent, other.Parent) - && Equals(Trackers, other.Trackers) - && Equals(CustomFields, other.CustomFields) - && Equals(IssueCategories, other.IssueCategories) - && Equals(EnabledModules, other.EnabledModules) - && Equals(TimeEntryActivities, other.TimeEntryActivities); + && DefaultAssignee == other.DefaultAssignee + && DefaultVersion == other.DefaultVersion + && Parent == other.Parent + && (Trackers?.Equals(other.Trackers) ?? other.Trackers == null) + && (CustomFields?.Equals(other.CustomFields) ?? other.CustomFields == null) + && (IssueCategories?.Equals(other.IssueCategories) ?? other.IssueCategories == null) + && (EnabledModules?.Equals(other.EnabledModules) ?? other.EnabledModules == null) + && (TimeEntryActivities?.Equals(other.TimeEntryActivities) ?? other.TimeEntryActivities == null); } /// diff --git a/src/redmine-net-api/Types/ProjectMembership.cs b/src/redmine-net-api/Types/ProjectMembership.cs index 8a5fd6ce..8dd3642a 100644 --- a/src/redmine-net-api/Types/ProjectMembership.cs +++ b/src/redmine-net-api/Types/ProjectMembership.cs @@ -162,10 +162,10 @@ public override bool Equals(ProjectMembership other) { if (other == null) return false; return Id == other.Id - && Equals(Project, other.Project) - && Equals(Roles, other.Roles) - && Equals(User, other.User) - && Equals(Group, other.Group); + && Project == other.Project + && User == other.User + && Group == other.Group + && Roles != null ? Roles.Equals(other.Roles) : other.Roles == null; } /// diff --git a/src/redmine-net-api/Types/Role.cs b/src/redmine-net-api/Types/Role.cs index 4998f2a4..10a2420b 100644 --- a/src/redmine-net-api/Types/Role.cs +++ b/src/redmine-net-api/Types/Role.cs @@ -137,13 +137,13 @@ public override void ReadJson(JsonReader reader) public bool Equals(Role other) { if (other == null) return false; - return EqualityComparer.Default.Equals(Id, other.Id) && - EqualityComparer.Default.Equals(Name, other.Name) && - IsAssignable == other.IsAssignable && - EqualityComparer.Default.Equals(IssuesVisibility, other.IssuesVisibility) && - EqualityComparer.Default.Equals(TimeEntriesVisibility, other.TimeEntriesVisibility) && - EqualityComparer.Default.Equals(UsersVisibility, other.UsersVisibility) && - EqualityComparer>.Default.Equals(Permissions, other.Permissions); + return Id == other.Id + && string.Equals(Name, other.Name, StringComparison.Ordinal) + && IsAssignable == other.IsAssignable + && IssuesVisibility == other.IssuesVisibility + && TimeEntriesVisibility == other.TimeEntriesVisibility + && UsersVisibility == other.UsersVisibility + && Permissions != null ? Permissions.Equals(other.Permissions) : other.Permissions == null; } diff --git a/src/redmine-net-api/Types/Search.cs b/src/redmine-net-api/Types/Search.cs index 72aa88d7..71ba4885 100644 --- a/src/redmine-net-api/Types/Search.cs +++ b/src/redmine-net-api/Types/Search.cs @@ -124,10 +124,10 @@ public bool Equals(Search other) { if (other == null) return false; return Id == other.Id - && string.Equals(Title, other.Title, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(Url, other.Url, StringComparison.OrdinalIgnoreCase) - && string.Equals(Type, other.Type, StringComparison.OrdinalIgnoreCase) + && string.Equals(Title, other.Title, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(Url, other.Url, StringComparison.Ordinal) + && string.Equals(Type, other.Type, StringComparison.Ordinal) && DateTime == other.DateTime; } diff --git a/src/redmine-net-api/Types/TimeEntry.cs b/src/redmine-net-api/Types/TimeEntry.cs index afb8d110..79ad91bd 100644 --- a/src/redmine-net-api/Types/TimeEntry.cs +++ b/src/redmine-net-api/Types/TimeEntry.cs @@ -233,11 +233,11 @@ public override bool Equals(TimeEntry other) && SpentOn == other.SpentOn && Hours == other.Hours && Activity == other.Activity - && Comments == other.Comments + && string.Equals(Comments, other.Comments, StringComparison.Ordinal) && User == other.User && CreatedOn == other.CreatedOn && UpdatedOn == other.UpdatedOn - && Equals(CustomFields, other.CustomFields); + && (CustomFields?.Equals(other.CustomFields) ?? other.CustomFields == null); } /// diff --git a/src/redmine-net-api/Types/TimeEntryActivity.cs b/src/redmine-net-api/Types/TimeEntryActivity.cs index dbc6afe3..6e0ef6c7 100644 --- a/src/redmine-net-api/Types/TimeEntryActivity.cs +++ b/src/redmine-net-api/Types/TimeEntryActivity.cs @@ -132,7 +132,9 @@ public bool Equals(TimeEntryActivity other) { if (other == null) return false; - return Id == other.Id && Name == other.Name && IsDefault == other.IsDefault && IsActive == other.IsActive; + return base.Equals(other) + && IsDefault == other.IsDefault + && IsActive == other.IsActive; } /// diff --git a/src/redmine-net-api/Types/Tracker.cs b/src/redmine-net-api/Types/Tracker.cs index 891ae4d0..13d26b31 100644 --- a/src/redmine-net-api/Types/Tracker.cs +++ b/src/redmine-net-api/Types/Tracker.cs @@ -121,7 +121,10 @@ public bool Equals(Tracker other) { if (other == null) return false; - return Id == other.Id && Name == other.Name; + return base.Equals(other) + && DefaultStatus == other.DefaultStatus + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && EnabledStandardFields != null ? EnabledStandardFields.Equals(other.EnabledStandardFields) : other.EnabledStandardFields != null; } /// diff --git a/src/redmine-net-api/Types/TrackerCoreField.cs b/src/redmine-net-api/Types/TrackerCoreField.cs index f5e7f6d2..54b5f1be 100644 --- a/src/redmine-net-api/Types/TrackerCoreField.cs +++ b/src/redmine-net-api/Types/TrackerCoreField.cs @@ -16,6 +16,17 @@ namespace Redmine.Net.Api.Types [XmlRoot(RedmineKeys.FIELD)] public sealed class TrackerCoreField: IXmlSerializable, IJsonSerializable, IEquatable { + /// + /// + /// + public TrackerCoreField() + { + } + + internal TrackerCoreField(string name) + { + Name = name; + } /// /// /// @@ -94,7 +105,7 @@ public void ReadJson(JsonReader reader) /// public bool Equals(TrackerCoreField other) { - return other != null && Name == other.Name; + return other != null && string.Equals(Name, other.Name, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/Upload.cs b/src/redmine-net-api/Types/Upload.cs index ca2ac4ee..be702c1f 100644 --- a/src/redmine-net-api/Types/Upload.cs +++ b/src/redmine-net-api/Types/Upload.cs @@ -170,10 +170,10 @@ public void WriteJson(JsonWriter writer) public bool Equals(Upload other) { return other != null - && string.Equals(Token, other.Token, StringComparison.OrdinalIgnoreCase) - && string.Equals(FileName, other.FileName, StringComparison.OrdinalIgnoreCase) - && string.Equals(Description, other.Description, StringComparison.OrdinalIgnoreCase) - && string.Equals(ContentType, other.ContentType, StringComparison.OrdinalIgnoreCase); + && string.Equals(Token, other.Token, StringComparison.Ordinal) + && string.Equals(FileName, other.FileName, StringComparison.Ordinal) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && string.Equals(ContentType, other.ContentType, StringComparison.Ordinal); } /// diff --git a/src/redmine-net-api/Types/User.cs b/src/redmine-net-api/Types/User.cs index 7633af2a..e47ae005 100644 --- a/src/redmine-net-api/Types/User.cs +++ b/src/redmine-net-api/Types/User.cs @@ -331,25 +331,25 @@ public override bool Equals(User other) { if (other == null) return false; return Id == other.Id - && string.Equals(AvatarUrl,other.AvatarUrl, StringComparison.OrdinalIgnoreCase) - && string.Equals(Login,other.Login, StringComparison.OrdinalIgnoreCase) - && string.Equals(FirstName,other.FirstName, StringComparison.OrdinalIgnoreCase) - && string.Equals(LastName,other.LastName, StringComparison.OrdinalIgnoreCase) - && string.Equals(Email,other.Email, StringComparison.OrdinalIgnoreCase) - && string.Equals(MailNotification,other.MailNotification, StringComparison.OrdinalIgnoreCase) - && string.Equals(ApiKey,other.ApiKey, StringComparison.OrdinalIgnoreCase) + && string.Equals(AvatarUrl,other.AvatarUrl, StringComparison.Ordinal) + && string.Equals(Login,other.Login, StringComparison.Ordinal) + && string.Equals(FirstName,other.FirstName, StringComparison.Ordinal) + && string.Equals(LastName,other.LastName, StringComparison.Ordinal) + && string.Equals(Email,other.Email, StringComparison.Ordinal) + && string.Equals(MailNotification,other.MailNotification, StringComparison.Ordinal) + && string.Equals(ApiKey,other.ApiKey, StringComparison.Ordinal) + && string.Equals(TwoFactorAuthenticationScheme,other.TwoFactorAuthenticationScheme, StringComparison.Ordinal) && AuthenticationModeId == other.AuthenticationModeId && CreatedOn == other.CreatedOn && LastLoginOn == other.LastLoginOn && Status == other.Status && MustChangePassword == other.MustChangePassword - && Equals(CustomFields, other.CustomFields) - && Equals(Memberships, other.Memberships) - && Equals(Groups, other.Groups) - && string.Equals(TwoFactorAuthenticationScheme,other.TwoFactorAuthenticationScheme, StringComparison.OrdinalIgnoreCase) && IsAdmin == other.IsAdmin && PasswordChangedOn == other.PasswordChangedOn - && UpdatedOn == other.UpdatedOn; + && UpdatedOn == other.UpdatedOn + && CustomFields != null ? CustomFields.Equals(other.CustomFields) : other.CustomFields == null + && Memberships != null ? Memberships.Equals(other.Memberships) : other.Memberships == null + && Groups != null ? Groups.Equals(other.Groups) : other.Groups == null; } /// @@ -373,27 +373,27 @@ public override int GetHashCode() { unchecked { - var hashCode = base.GetHashCode(); + var hashCode = 17; + hashCode = HashCodeHelper.GetHashCode(Id, hashCode); hashCode = HashCodeHelper.GetHashCode(AvatarUrl, hashCode); hashCode = HashCodeHelper.GetHashCode(Login, hashCode); - hashCode = HashCodeHelper.GetHashCode(Password, hashCode); hashCode = HashCodeHelper.GetHashCode(FirstName, hashCode); hashCode = HashCodeHelper.GetHashCode(LastName, hashCode); hashCode = HashCodeHelper.GetHashCode(Email, hashCode); hashCode = HashCodeHelper.GetHashCode(MailNotification, hashCode); + hashCode = HashCodeHelper.GetHashCode(ApiKey, hashCode); + hashCode = HashCodeHelper.GetHashCode(TwoFactorAuthenticationScheme, hashCode); hashCode = HashCodeHelper.GetHashCode(AuthenticationModeId, hashCode); hashCode = HashCodeHelper.GetHashCode(CreatedOn, hashCode); hashCode = HashCodeHelper.GetHashCode(LastLoginOn, hashCode); - hashCode = HashCodeHelper.GetHashCode(ApiKey, hashCode); hashCode = HashCodeHelper.GetHashCode(Status, hashCode); hashCode = HashCodeHelper.GetHashCode(MustChangePassword, hashCode); - hashCode = HashCodeHelper.GetHashCode(CustomFields, hashCode); - hashCode = HashCodeHelper.GetHashCode(Memberships, hashCode); - hashCode = HashCodeHelper.GetHashCode(Groups, hashCode); - hashCode = HashCodeHelper.GetHashCode(TwoFactorAuthenticationScheme, hashCode); hashCode = HashCodeHelper.GetHashCode(IsAdmin, hashCode); hashCode = HashCodeHelper.GetHashCode(PasswordChangedOn, hashCode); hashCode = HashCodeHelper.GetHashCode(UpdatedOn, hashCode); + hashCode = HashCodeHelper.GetHashCode(CustomFields, hashCode); + hashCode = HashCodeHelper.GetHashCode(Memberships, hashCode); + hashCode = HashCodeHelper.GetHashCode(Groups, hashCode); return hashCode; } } diff --git a/src/redmine-net-api/Types/Version.cs b/src/redmine-net-api/Types/Version.cs index b03d52bb..2861d4b3 100644 --- a/src/redmine-net-api/Types/Version.cs +++ b/src/redmine-net-api/Types/Version.cs @@ -230,16 +230,16 @@ public override void WriteJson(JsonWriter writer) public override bool Equals(Version other) { if (other == null) return false; - return Id == other.Id && Name == other.Name + return base.Equals(other) && Project == other.Project - && Description == other.Description - && Status == other.Status - && DueDate == other.DueDate - && Sharing == other.Sharing - && CreatedOn == other.CreatedOn - && UpdatedOn == other.UpdatedOn - && Equals(CustomFields, other.CustomFields) - && string.Equals(WikiPageTitle,other.WikiPageTitle, StringComparison.OrdinalIgnoreCase) + && string.Equals(Description, other.Description, StringComparison.Ordinal) + && Status == other.Status + && DueDate == other.DueDate + && Sharing == other.Sharing + && CreatedOn == other.CreatedOn + && UpdatedOn == other.UpdatedOn + && (CustomFields?.Equals(other.CustomFields) ?? other.CustomFields == null) + && string.Equals(WikiPageTitle,other.WikiPageTitle, StringComparison.Ordinal) && EstimatedHours == other.EstimatedHours && SpentHours == other.SpentHours; } diff --git a/src/redmine-net-api/Types/WikiPage.cs b/src/redmine-net-api/Types/WikiPage.cs index e5acd51b..d62aeb63 100644 --- a/src/redmine-net-api/Types/WikiPage.cs +++ b/src/redmine-net-api/Types/WikiPage.cs @@ -219,11 +219,12 @@ public override bool Equals(WikiPage other) && string.Equals(Title, other.Title, StringComparison.Ordinal) && string.Equals(Text, other.Text, StringComparison.Ordinal) && string.Equals(Comments, other.Comments, StringComparison.Ordinal) + && string.Equals(ParentTitle, other.ParentTitle, StringComparison.Ordinal) && Version == other.Version - && Equals(Author, other.Author) - && CreatedOn.Equals(other.CreatedOn) - && UpdatedOn.Equals(other.UpdatedOn) - && Equals(Attachments, other.Attachments); + && Author == other.Author + && CreatedOn == other.CreatedOn + && UpdatedOn == other.UpdatedOn + && (Attachments?.Equals(other.Attachments) ?? other.Attachments == null); } /// From ddca03d5a1835c282700997b0f4e973a0f8c219a Mon Sep 17 00:00:00 2001 From: zapadi Date: Wed, 12 Mar 2025 22:37:44 +0200 Subject: [PATCH 02/14] [Packages] Bump up --- .../redmine-net-api.Tests.csproj | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/redmine-net-api.Tests/redmine-net-api.Tests.csproj b/tests/redmine-net-api.Tests/redmine-net-api.Tests.csproj index 90f40281..87deb601 100644 --- a/tests/redmine-net-api.Tests/redmine-net-api.Tests.csproj +++ b/tests/redmine-net-api.Tests/redmine-net-api.Tests.csproj @@ -54,17 +54,17 @@ - - - - - - - + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive From bd46455e3b852345d24f9ef394f56fb855bfe3ec Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 22:52:14 +0200 Subject: [PATCH 03/14] [Tests] Fix namespace --- tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs | 2 +- .../Infrastructure/Order/CollectionOrderer.cs | 2 +- .../Infrastructure/Order/OrderAttribute.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs b/tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs index 97c849af..97ddb56a 100644 --- a/tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs +++ b/tests/redmine-net-api.Tests/Infrastructure/Order/CaseOrder.cs @@ -6,7 +6,7 @@ using Xunit.Abstractions; using Xunit.Sdk; -namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order { /// /// Custom xUnit test case orderer that uses the OrderAttribute diff --git a/tests/redmine-net-api.Tests/Infrastructure/Order/CollectionOrderer.cs b/tests/redmine-net-api.Tests/Infrastructure/Order/CollectionOrderer.cs index abe9cd91..ae7b01da 100644 --- a/tests/redmine-net-api.Tests/Infrastructure/Order/CollectionOrderer.cs +++ b/tests/redmine-net-api.Tests/Infrastructure/Order/CollectionOrderer.cs @@ -7,7 +7,7 @@ using Xunit; using Xunit.Abstractions; -namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order { /// /// Custom xUnit test collection orderer that uses the OrderAttribute diff --git a/tests/redmine-net-api.Tests/Infrastructure/Order/OrderAttribute.cs b/tests/redmine-net-api.Tests/Infrastructure/Order/OrderAttribute.cs index bc837971..c8f07627 100644 --- a/tests/redmine-net-api.Tests/Infrastructure/Order/OrderAttribute.cs +++ b/tests/redmine-net-api.Tests/Infrastructure/Order/OrderAttribute.cs @@ -1,6 +1,6 @@ using System; -namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order { public sealed class OrderAttribute : Attribute { From 0f6679bb6365929bb0d8ec7bb7a03833b079477a Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 23:10:43 +0200 Subject: [PATCH 04/14] [Tests] Host --- .../Tests/{HostValidationTests.cs => HostTests.cs} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename tests/redmine-net-api.Tests/Tests/{HostValidationTests.cs => HostTests.cs} (96%) diff --git a/tests/redmine-net-api.Tests/Tests/HostValidationTests.cs b/tests/redmine-net-api.Tests/Tests/HostTests.cs similarity index 96% rename from tests/redmine-net-api.Tests/Tests/HostValidationTests.cs rename to tests/redmine-net-api.Tests/Tests/HostTests.cs index 32448c72..89943a7c 100644 --- a/tests/redmine-net-api.Tests/Tests/HostValidationTests.cs +++ b/tests/redmine-net-api.Tests/Tests/HostTests.cs @@ -1,4 +1,4 @@ -using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Order; using Redmine.Net.Api; using Redmine.Net.Api.Exceptions; using Xunit; @@ -7,7 +7,7 @@ namespace Padi.DotNet.RedmineAPI.Tests.Tests { [Trait("Redmine-api", "Host")] [Order(1)] - public sealed class HostValidationTests + public sealed class HostTests { [Theory] [InlineData(null)] @@ -54,15 +54,15 @@ public void Should_Throw_Redmine_Exception_When_Host_Is_Invalid(string host) [InlineData("www.domain.com:3000", "https://www.domain.com:3000/")] [InlineData("https://www.google.com", "https://www.google.com/")] [InlineData("http://example.com:8080", "http://example.com:8080/")] - [InlineData("http://example.com/path", "http://example.com/")] + [InlineData("http://example.com/path", "http://example.com/path")] [InlineData("http://example.com?param=value", "http://example.com/")] [InlineData("http://example.com#fragment", "http://example.com/")] [InlineData("http://example.com/", "http://example.com/")] [InlineData("http://example.com/?param=value", "http://example.com/")] [InlineData("http://example.com/#fragment", "http://example.com/")] - [InlineData("http://example.com/path/page", "http://example.com/")] - [InlineData("http://example.com/path/page?param=value", "http://example.com/")] - [InlineData("http://example.com/path/page#fragment","http://example.com/")] + [InlineData("http://example.com/path/page", "http://example.com/path/page")] + [InlineData("http://example.com/path/page?param=value", "http://example.com/path/page")] + [InlineData("http://example.com/path/page#fragment","http://example.com/path/page")] [InlineData("http://[::1]:8080", "http://[::1]/")] [InlineData("http://www.domain.com/title/index.htm", "http://www.domain.com/")] [InlineData("http://www.localhost.com/", "http://www.localhost.com/")] From 9a68b0e8a13def7e80058ea22dc19aa9e77937c2 Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 22:54:48 +0200 Subject: [PATCH 05/14] [Tests] RedmineFixture --- .../Infrastructure/Collections/RedmineCollection.cs | 10 ++++++++++ .../redmine-net-api.Tests/Infrastructure/Constants.cs | 8 ++++++++ .../Infrastructure/{ => Fixtures}/RedmineFixture.cs | 11 ++++++----- .../Infrastructure/RedmineCollection.cs | 9 --------- 4 files changed, 24 insertions(+), 14 deletions(-) create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Collections/RedmineCollection.cs create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Constants.cs rename tests/redmine-net-api.Tests/Infrastructure/{ => Fixtures}/RedmineFixture.cs (71%) delete mode 100644 tests/redmine-net-api.Tests/Infrastructure/RedmineCollection.cs diff --git a/tests/redmine-net-api.Tests/Infrastructure/Collections/RedmineCollection.cs b/tests/redmine-net-api.Tests/Infrastructure/Collections/RedmineCollection.cs new file mode 100644 index 00000000..8a30da0c --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Collections/RedmineCollection.cs @@ -0,0 +1,10 @@ +#if !(NET20 || NET40) +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Collections +{ + [CollectionDefinition(Constants.RedmineCollection)] + public sealed class RedmineCollection : ICollectionFixture { } +} +#endif \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Infrastructure/Constants.cs b/tests/redmine-net-api.Tests/Infrastructure/Constants.cs new file mode 100644 index 00000000..f680e785 --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Constants.cs @@ -0,0 +1,8 @@ +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure; + +public static class Constants +{ + public const string XmlRedmineSerializerCollection = "XmlRedmineSerializerCollection"; + public const string JsonRedmineSerializerCollection = "JsonRedmineSerializerCollection"; + public const string RedmineCollection = "RedmineCollection"; +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Infrastructure/RedmineFixture.cs b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineFixture.cs similarity index 71% rename from tests/redmine-net-api.Tests/Infrastructure/RedmineFixture.cs rename to tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineFixture.cs index cbcfbc63..6865c398 100644 --- a/tests/redmine-net-api.Tests/Infrastructure/RedmineFixture.cs +++ b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineFixture.cs @@ -1,9 +1,8 @@ using System.Diagnostics; using Redmine.Net.Api; -using Redmine.Net.Api.Authentication; using Redmine.Net.Api.Serialization; -namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures { public sealed class RedmineFixture { @@ -17,23 +16,25 @@ public RedmineFixture () Credentials = TestHelper.GetApplicationConfiguration(); _redmineManagerOptionsBuilder = new RedmineManagerOptionsBuilder() - .WithHost(Credentials.Uri) + .WithHost(Credentials.Uri ?? "localhost") .WithApiKeyAuthentication(Credentials.ApiKey); SetMimeTypeXml(); SetMimeTypeJson(); + + RedmineManager = new RedmineManager(_redmineManagerOptionsBuilder); } [Conditional("DEBUG_JSON")] private void SetMimeTypeJson() { - RedmineManager = new RedmineManager(_redmineManagerOptionsBuilder.WithSerializationType(SerializationType.Json)); + _redmineManagerOptionsBuilder.WithSerializationType(SerializationType.Json); } [Conditional("DEBUG_XML")] private void SetMimeTypeXml() { - RedmineManager = new RedmineManager(_redmineManagerOptionsBuilder.WithSerializationType(SerializationType.Xml)); + _redmineManagerOptionsBuilder.WithSerializationType(SerializationType.Xml); } } } \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Infrastructure/RedmineCollection.cs b/tests/redmine-net-api.Tests/Infrastructure/RedmineCollection.cs deleted file mode 100644 index fa2bcbd4..00000000 --- a/tests/redmine-net-api.Tests/Infrastructure/RedmineCollection.cs +++ /dev/null @@ -1,9 +0,0 @@ -#if !(NET20 || NET40) -using Xunit; - -namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure -{ - [CollectionDefinition("RedmineCollection")] - public sealed class RedmineCollection : ICollectionFixture { } -} -#endif \ No newline at end of file From 8ab4802398501fa92de073bfef351dc0127ba510 Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 22:57:51 +0200 Subject: [PATCH 06/14] [Tests] XmlSerializerFixture --- .../Collections/XmlRedmineSerializerCollection.cs | 7 +++++++ .../Infrastructure/Fixtures/XmlSerializerFixture.cs | 8 ++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Collections/XmlRedmineSerializerCollection.cs create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Fixtures/XmlSerializerFixture.cs diff --git a/tests/redmine-net-api.Tests/Infrastructure/Collections/XmlRedmineSerializerCollection.cs b/tests/redmine-net-api.Tests/Infrastructure/Collections/XmlRedmineSerializerCollection.cs new file mode 100644 index 00000000..02ca7492 --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Collections/XmlRedmineSerializerCollection.cs @@ -0,0 +1,7 @@ +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Collections; + +[CollectionDefinition(Constants.XmlRedmineSerializerCollection)] +public sealed class XmlRedmineSerializerCollection : ICollectionFixture { } \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Infrastructure/Fixtures/XmlSerializerFixture.cs b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/XmlSerializerFixture.cs new file mode 100644 index 00000000..700329e1 --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/XmlSerializerFixture.cs @@ -0,0 +1,8 @@ +using Redmine.Net.Api.Serialization; + +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; + +public sealed class XmlSerializerFixture +{ + internal IRedmineSerializer Serializer { get; private set; } = new XmlRedmineSerializer(); +} \ No newline at end of file From 12ed9fde52d19394d77f4c60e15ca053efdfd611 Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 22:58:47 +0200 Subject: [PATCH 07/14] [Tests] JsonSerializerFixture --- .../Collections/JsonRedmineSerializerCollection.cs | 7 +++++++ .../Infrastructure/Fixtures/JsonSerializerFixture.cs | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Collections/JsonRedmineSerializerCollection.cs create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Fixtures/JsonSerializerFixture.cs diff --git a/tests/redmine-net-api.Tests/Infrastructure/Collections/JsonRedmineSerializerCollection.cs b/tests/redmine-net-api.Tests/Infrastructure/Collections/JsonRedmineSerializerCollection.cs new file mode 100644 index 00000000..45b0fe86 --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Collections/JsonRedmineSerializerCollection.cs @@ -0,0 +1,7 @@ +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Collections; + +[CollectionDefinition(Constants.JsonRedmineSerializerCollection)] +public sealed class JsonRedmineSerializerCollection : ICollectionFixture { } \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Infrastructure/Fixtures/JsonSerializerFixture.cs b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/JsonSerializerFixture.cs new file mode 100644 index 00000000..d787dd62 --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/JsonSerializerFixture.cs @@ -0,0 +1,9 @@ +using Redmine.Net.Api.Serialization; + +namespace Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; + +public sealed class JsonSerializerFixture +{ + internal IRedmineSerializer Serializer { get; private set; } = new JsonRedmineSerializer(); + +} \ No newline at end of file From 8b8b311ad458b99a2f0492971b354b04108b3977 Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 23:00:17 +0200 Subject: [PATCH 08/14] [Tests] Xml deserializer tests --- .../Serialization/Xml/AttachmentTests.cs | 43 ++++ .../Serialization/Xml/CustomFieldTests.cs | 65 ++++++ .../Serialization/Xml/EnumerationTests.cs | 117 ++++++++++ .../Serialization/Xml/ErrorTests.cs | 32 +++ .../Serialization/Xml/FileTests.cs | 87 ++++++++ .../Serialization/Xml/GroupTests.cs | 67 ++++++ .../Serialization/Xml/IssueCategoryTests.cs | 73 +++++++ .../Serialization/Xml/IssueStatusTests.cs | 47 ++++ .../Serialization/Xml/IssueTests.cs | 204 ++++++++++++++++++ .../Serialization/Xml/MembershipTests.cs | 95 ++++++++ .../Serialization/Xml/MyAccountTests.cs | 79 +++++++ .../Serialization/Xml/NewsTests.cs | 67 ++++++ .../Serialization/Xml/ProjectTests.cs | 87 ++++++++ .../Serialization/Xml/QueryTests.cs | 51 +++++ .../Serialization/Xml/RelationTests.cs | 81 +++++++ .../Serialization/Xml/RoleTests.cs | 95 ++++++++ .../Serialization/Xml/SearchTests.cs | 59 +++++ .../Serialization/Xml/TrackerTests.cs | 134 ++++++++++++ .../Serialization/Xml/UploadTests.cs | 62 ++++++ .../Serialization/Xml/UserTests.cs | 159 ++++++++++++++ .../Serialization/Xml/VersionTests.cs | 111 ++++++++++ .../Serialization/Xml/WikiTests.cs | 86 ++++++++ 22 files changed, 1901 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/AttachmentTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/CustomFieldTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/EnumerationTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/ErrorTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/FileTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/GroupTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/IssueCategoryTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/IssueStatusTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/IssueTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/MembershipTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/MyAccountTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/NewsTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/ProjectTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/QueryTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/RelationTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/RoleTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/SearchTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/TrackerTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/UploadTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/UserTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/VersionTests.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Xml/WikiTests.cs diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/AttachmentTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/AttachmentTests.cs new file mode 100644 index 00000000..85caa2ae --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/AttachmentTests.cs @@ -0,0 +1,43 @@ +using System; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class AttachmentTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Attachment() + { + const string input = """ + + + 6243 + test.txt + 124 + text/plain + This is an attachment + http://localhost:3000/attachments/download/6243/test.txt + + 2011-07-18T22:58:40+02:00 + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(6243, output.Id); + Assert.Equal("test.txt", output.FileName); + Assert.Equal(124, output.FileSize); + Assert.Equal("text/plain", output.ContentType); + Assert.Equal("This is an attachment", output.Description); + Assert.Equal("http://localhost:3000/attachments/download/6243/test.txt", output.ContentUrl); + Assert.Equal("Jean-Philippe Lang", output.Author.Name); + Assert.Equal(1, output.Author.Id); + Assert.Equal(new DateTime(2011, 7, 18, 20, 58, 40, DateTimeKind.Utc).ToLocalTime(), output.CreatedOn); + + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/CustomFieldTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/CustomFieldTests.cs new file mode 100644 index 00000000..0cb541a9 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/CustomFieldTests.cs @@ -0,0 +1,65 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public sealed class CustomFieldTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_CustomFields() + { + const string input = """ + + + + 1 + Affected version + issue + list + + + + true + true + true + true + + false + + + 0.5.x + + + 0.6.x + + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(1, output.TotalItems); + + var customFields = output.Items.ToList(); + Assert.Equal(1, customFields[0].Id); + Assert.Equal("Affected version", customFields[0].Name); + Assert.Equal("issue", customFields[0].CustomizedType); + Assert.Equal("list", customFields[0].FieldFormat); + Assert.True(customFields[0].IsRequired); + Assert.True(customFields[0].IsFilter); + Assert.True(customFields[0].Searchable); + Assert.True(customFields[0].Multiple); + Assert.False(customFields[0].Visible); + + var possibleValues = customFields[0].PossibleValues.ToList(); + Assert.Equal(2, possibleValues.Count); + Assert.Equal("0.5.x", possibleValues[0].Value); + Assert.Equal("0.6.x", possibleValues[1].Value); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/EnumerationTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/EnumerationTests.cs new file mode 100644 index 00000000..2d870e8c --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/EnumerationTests.cs @@ -0,0 +1,117 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class EnumerationTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Issue_Priorities() + { + const string input = """ + + + + 3 + Low + false + + + 4 + Normal + true + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var issuePriorities = output.Items.ToList(); + Assert.Equal(2, issuePriorities.Count); + + Assert.Equal(3, issuePriorities[0].Id); + Assert.Equal("Low", issuePriorities[0].Name); + Assert.False(issuePriorities[0].IsDefault); + + Assert.Equal(4, issuePriorities[1].Id); + Assert.Equal("Normal", issuePriorities[1].Name); + Assert.True(issuePriorities[1].IsDefault); + } + + [Fact] + public void Should_Deserialize_TimeEntry_Activities() + { + const string input = """ + + + + 8 + Design + false + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Single(output.Items); + + var timeEntryActivities = output.Items.ToList(); + Assert.Equal(8, timeEntryActivities[0].Id); + Assert.Equal("Design", timeEntryActivities[0].Name); + Assert.False(timeEntryActivities[0].IsDefault); + } + + [Fact] + public void Should_Deserialize_Document_Categories() + { + const string input = """ + + + + 1 + Uncategorized + false + + + 2 + User documentation + false + + + 3 + Technical documentation + false + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(3, output.TotalItems); + + var documentCategories = output.Items.ToList(); + Assert.Equal(3, documentCategories.Count); + + Assert.Equal(1, documentCategories[0].Id); + Assert.Equal("Uncategorized", documentCategories[0].Name); + Assert.False(documentCategories[0].IsDefault); + + Assert.Equal(2, documentCategories[1].Id); + Assert.Equal("User documentation", documentCategories[1].Name); + Assert.False(documentCategories[1].IsDefault); + + Assert.Equal(3, documentCategories[2].Id); + Assert.Equal("Technical documentation", documentCategories[2].Name); + Assert.False(documentCategories[2].IsDefault); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/ErrorTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/ErrorTests.cs new file mode 100644 index 00000000..c5e447a5 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/ErrorTests.cs @@ -0,0 +1,32 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public sealed class ErrorTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Errors() + { + const string input = """ + + First name can't be blank + Email is invalid + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var errors = output.Items.ToList(); + Assert.Equal("First name can't be blank", errors[0].Info); + Assert.Equal("Email is invalid", errors[1].Info); + + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/FileTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/FileTests.cs new file mode 100644 index 00000000..72fbd208 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/FileTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class FileTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_File() + { + const string input = """ + + + 12 + foo-1.0-setup.exe + 74753799 + application/octet-stream + Foo App for Windows + http://localhost:3000/attachments/download/12/foo-1.0-setup.exe + + 2017-01-04T09:12:32Z + + 1276481102f218c981e0324180bafd9f + 12 + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.NotNull(output); + Assert.Equal(12, output.Id); + Assert.Equal("foo-1.0-setup.exe", output.Filename); + Assert.Equal("application/octet-stream", output.ContentType); + Assert.Equal("Foo App for Windows", output.Description); + Assert.Equal("http://localhost:3000/attachments/download/12/foo-1.0-setup.exe", output.ContentUrl); + Assert.Equal(1, output.Author.Id); + Assert.Equal("Redmine Admin", output.Author.Name); + Assert.Equal(new DateTimeOffset(new DateTime(2017,01,04,09,12,32, DateTimeKind.Utc)), new DateTimeOffset(output.CreatedOn!.Value)); + Assert.Equal(2, output.Version.Id); + Assert.Equal("1.0", output.Version.Name); + Assert.Equal("1276481102f218c981e0324180bafd9f", output.Digest); + Assert.Equal(12, output.Downloads); + } + + [Fact] + public void Should_Deserialize_Files() + { + const string input = """ + + + + 12 + foo-1.0-setup.exe + 74753799 + application/octet-stream + Foo App for Windows + http://localhost:3000/attachments/download/12/foo-1.0-setup.exe + + 2017-01-04T09:12:32Z + + 1276481102f218c981e0324180bafd9f + 12 + + + 11 + foo-1.0.dmg + 6886287 + application/x-octet-stream + Foo App for macOS + http://localhost:3000/attachments/download/11/foo-1.0.dmg + + 2017-01-04T09:12:07Z + + 14758f1afd44c09b7992073ccf00b43d + 5 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + Assert.NotNull(output); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/GroupTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/GroupTests.cs new file mode 100644 index 00000000..3e057eac --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/GroupTests.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class GroupTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Group() + { + const string input = """ + + 20 + Developers + + + + + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.NotNull(output); + Assert.Equal(20, output.Id); + Assert.Equal("Developers", output.Name); + Assert.NotNull(output.Users); + Assert.Equal(2, output.Users.Count); + Assert.Equal("John Smith", output.Users[0].Name); + Assert.Equal("Dave Loper", output.Users[1].Name); + Assert.Equal(5, output.Users[0].Id); + Assert.Equal(8, output.Users[1].Id); + } + + [Fact] + public void Should_Deserialize_Groups() + { + const string input = """ + + + + 53 + Managers + + + 55 + Developers + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var groups = output.Items.ToList(); + Assert.Equal(53, groups[0].Id); + Assert.Equal("Managers", groups[0].Name); + + Assert.Equal(55, groups[1].Id); + Assert.Equal("Developers", groups[1].Name); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/IssueCategoryTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/IssueCategoryTests.cs new file mode 100644 index 00000000..9bf24bdb --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/IssueCategoryTests.cs @@ -0,0 +1,73 @@ +using System.Collections.Generic; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class IssueCategoryTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Issue_Category() + { + const string input = """ + + + 2 + + UI + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(2, output.Id); + Assert.Equal("Redmine", output.Project.Name); + Assert.Equal(1, output.Project.Id); + Assert.Equal("UI", output.Name); + } + + [Fact] + public void Should_Deserialize_Issue_Categories() + { + const string input = """ + + + + 57 + + UI + + + + 58 + + Test + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var issueCategories = output.Items.ToList(); + Assert.Equal(2, issueCategories.Count); + + Assert.Equal(57, issueCategories[0].Id); + Assert.Equal("Foo", issueCategories[0].Project.Name); + Assert.Equal(17, issueCategories[0].Project.Id); + Assert.Equal("UI", issueCategories[0].Name); + Assert.Equal("John Smith", issueCategories[0].AssignTo.Name); + Assert.Equal(22, issueCategories[0].AssignTo.Id); + + Assert.Equal(58, issueCategories[1].Id); + Assert.Equal("Foo", issueCategories[1].Project.Name); + Assert.Equal(17, issueCategories[1].Project.Id); + Assert.Equal("Test", issueCategories[1].Name); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/IssueStatusTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/IssueStatusTests.cs new file mode 100644 index 00000000..8e699d0b --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/IssueStatusTests.cs @@ -0,0 +1,47 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class IssueStatusTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Issue_Statuses() + { + const string input = """ + + + + 1 + New + false + + + 2 + Closed + true + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var issueStatuses = output.Items.ToList(); + Assert.Equal(2, issueStatuses.Count); + + Assert.Equal(1, issueStatuses[0].Id); + Assert.Equal("New", issueStatuses[0].Name); + Assert.False(issueStatuses[0].IsClosed); + + Assert.Equal(2, issueStatuses[1].Id); + Assert.Equal("Closed", issueStatuses[1].Name); + Assert.True(issueStatuses[1].IsClosed); + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/IssueTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/IssueTests.cs new file mode 100644 index 00000000..e326f8db --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/IssueTests.cs @@ -0,0 +1,204 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class IssueTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Issues() + { + const string input = """ + + + + 4326 + + + + + + + Aggregate Multiple Issue Changes for Email Notifications + + This is not to be confused with another useful proposed feature that + would do digest emails for notifications. + + 2009-12-03 + + 0 + + Thu Dec 03 15:02:12 +0100 2009 + Sun Jan 03 12:08:41 +0100 2010 + + + 4325 + + + + + + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(1640, output.TotalItems); + + var issues = output.Items.ToList(); + Assert.Equal(4326, issues[0].Id); + Assert.Equal("Redmine", issues[0].Project.Name); + Assert.Equal(1, issues[0].Project.Id); + Assert.Equal("Feature", issues[0].Tracker.Name); + Assert.Equal(2, issues[0].Tracker.Id); + Assert.Equal("New", issues[0].Status.Name); + Assert.Equal(1, issues[0].Status.Id); + Assert.Equal("Normal", issues[0].Priority.Name); + Assert.Equal(4, issues[0].Priority.Id); + Assert.Equal("John Smith", issues[0].Author.Name); + Assert.Equal(10106, issues[0].Author.Id); + Assert.Equal("Email notifications", issues[0].Category.Name); + Assert.Equal(9, issues[0].Category.Id); + Assert.Equal("Aggregate Multiple Issue Changes for Email Notifications", issues[0].Subject); + Assert.Contains("This is not to be confused with another useful proposed feature", issues[0].Description); + Assert.Equal(new DateTime(2009, 12, 3), issues[0].StartDate); + Assert.Null(issues[0].DueDate); + Assert.Equal(0, issues[0].DoneRatio); + Assert.Null(issues[0].EstimatedHours); + // Assert.Equal(new DateTime(2009, 12, 3, 14, 2, 12, DateTimeKind.Utc).ToLocalTime(), issues[0].CreatedOn); + // Assert.Equal(new DateTime(2010, 1, 3, 11, 8, 41, DateTimeKind.Utc).ToLocalTime(), issues[0].UpdatedOn); + + Assert.Equal(4325, issues[1].Id); + Assert.Null(issues[1].Journals); + Assert.Null(issues[1].ChangeSets); + Assert.Null(issues[1].CustomFields); + } + + [Fact] + public void Should_Deserialize_Issues_With_CustomFields() + { + const string input = """ + + + + 4326 + + + + + + + + Aggregate Multiple Issue Changes for Email Notifications + + + This is not to be confused with another useful proposed feature that + would do digest emails for notifications. + + 2009-12-03 + + 0 + + + Duplicate + Test + 1 + 2010-01-12 + + Thu Dec 03 15:02:12 +0100 2009 + Sun Jan 03 12:08:41 +0100 2010 + + + 4325 + + + + + + + 1.0.1 + + + Fixed + + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + } + + [Fact] + public void Should_Deserialize_Issue_With_Journals() + { + const string input = """ + + 1 + + + + + + Fixed in Revision 128 + 2007-01-01T05:21:00+01:00 +
+ + + + + 2009-08-13T11:33:17+02:00 +
+ + 5 + 8 + +
+
+ + + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(1, output.Id); + Assert.Equal("Redmine", output.Project.Name); + Assert.Equal(1, output.Project.Id); + Assert.Equal("Defect", output.Tracker.Name); + Assert.Equal(1, output.Tracker.Id); + + var journals = output.Journals.ToList(); + Assert.Equal(2, journals.Count); + + Assert.Equal(1, journals[0].Id); + Assert.Equal("Jean-Philippe Lang", journals[0].User.Name); + Assert.Equal(1, journals[0].User.Id); + Assert.Equal("Fixed in Revision 128", journals[0].Notes); + Assert.Equal(new DateTime(2007, 1, 1, 4, 21, 0, DateTimeKind.Utc).ToLocalTime(), journals[0].CreatedOn); + Assert.Null(journals[0].Details); + + Assert.Equal(10531, journals[1].Id); + Assert.Equal("efgh efgh", journals[1].User.Name); + Assert.Equal(7384, journals[1].User.Id); + Assert.Null(journals[1].Notes); + Assert.Equal(new DateTime(2009, 8, 13, 9, 33, 17, DateTimeKind.Utc).ToLocalTime(), journals[1].CreatedOn); + + var details = journals[1].Details.ToList(); + Assert.Single(details); + Assert.Equal("attr", details[0].Property); + Assert.Equal("status_id", details[0].Name); + Assert.Equal("5", details[0].OldValue); + Assert.Equal("8", details[0].NewValue); + + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/MembershipTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/MembershipTests.cs new file mode 100644 index 00000000..9bede534 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/MembershipTests.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public sealed class MembershipTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Memberships() + { + const string input = """ + + + + 1 + + + + + + + + 3 + + + + + + + + 4 + + + + + + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + } + + [Fact] + public void Should_Deserialize_Membership() + { + const string input = """ + + + 1 + + + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.Equal(1, output.Id); + Assert.Equal("Redmine", output.Project.Name); + Assert.Equal(1, output.Project.Id); + Assert.Equal("David Robert", output.User.Name); + Assert.Equal(17, output.User.Id); + } + + [Fact] + public void Should_Deserialize_Membership_With_Roles() + { + const string input = """ + + + 1 + + + + + + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.Equal(1, output.Id); + Assert.Equal("Redmine", output.Project.Name); + Assert.Equal(1, output.Project.Id); + Assert.Equal("David Robert", output.User.Name); + Assert.Equal(17, output.User.Id); + Assert.NotNull(output.Roles); + Assert.Single(output.Roles); + Assert.Equal("Manager", output.Roles[0].Name); + Assert.Equal(1, output.Roles[0].Id); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/MyAccountTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/MyAccountTests.cs new file mode 100644 index 00000000..ae1b70e2 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/MyAccountTests.cs @@ -0,0 +1,79 @@ +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class MyAccountTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_MyAccount() + { + const string input = """ + + + 3 + dlopper + false + Dave + Lopper + dlopper@somenet.foo + 2006-07-19T17:33:19Z + 2020-06-14T13:03:34Z + c308a59c9dea95920b13522fb3e0fb7fae4f292d + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.Equal(3, output.Id); + Assert.Equal("dlopper", output.Login); + Assert.False(output.IsAdmin); + Assert.Equal("Dave", output.FirstName); + Assert.Equal("Lopper", output.LastName); + Assert.Equal("dlopper@somenet.foo", output.Email); + Assert.Equal("c308a59c9dea95920b13522fb3e0fb7fae4f292d", output.ApiKey); + } + + [Fact] + public void Should_Deserialize_MyAccount_With_CustomFields() + { + const string input = """ + + + 3 + dlopper + false + Dave + Lopper + dlopper@somenet.foo + 2006-07-19T17:33:19Z + 2020-06-14T13:03:34Z + c308a59c9dea95920b13522fb3e0fb7fae4f292d + + + + + + + + + + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.Equal(3, output.Id); + Assert.Equal("dlopper", output.Login); + Assert.False(output.IsAdmin); + Assert.Equal("Dave", output.FirstName); + Assert.Equal("Lopper", output.LastName); + Assert.Equal("dlopper@somenet.foo", output.Email); + Assert.Equal("c308a59c9dea95920b13522fb3e0fb7fae4f292d", output.ApiKey); + Assert.NotNull(output.CustomFields); + Assert.Equal(2, output.CustomFields.Count); + Assert.Equal("Phone number", output.CustomFields[0].Name); + Assert.Equal(4, output.CustomFields[0].Id); + Assert.Equal("Money", output.CustomFields[1].Name); + Assert.Equal(5, output.CustomFields[1].Id); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/NewsTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/NewsTests.cs new file mode 100644 index 00000000..0b29de40 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/NewsTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class NewsTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_News() + { + const string input = """ + + + + 54 + + + Redmine 1.1.3 released + + Redmine 1.1.3 has been released + 2011-04-29T14:00:25+02:00 + + + 53 + + + Redmine 1.1.2 bug/security fix released + + Redmine 1.1.2 has been released + 2011-03-07T21:07:03+01:00 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var newsItems = output.Items.ToList(); + Assert.Equal(2, newsItems.Count); + + Assert.Equal(54, newsItems[0].Id); + Assert.Equal("Redmine", newsItems[0].Project.Name); + Assert.Equal(1, newsItems[0].Project.Id); + Assert.Equal("Jean-Philippe Lang", newsItems[0].Author.Name); + Assert.Equal(1, newsItems[0].Author.Id); + Assert.Equal("Redmine 1.1.3 released", newsItems[0].Title); + Assert.Equal("Redmine 1.1.3 has been released", newsItems[0].Description); + Assert.Equal(new DateTime(2011, 4, 29, 12, 0, 25, DateTimeKind.Utc).ToLocalTime(), newsItems[0].CreatedOn); + + Assert.Equal(53, newsItems[1].Id); + Assert.Equal("Redmine", newsItems[1].Project.Name); + Assert.Equal(1, newsItems[1].Project.Id); + Assert.Equal("Jean-Philippe Lang", newsItems[1].Author.Name); + Assert.Equal(1, newsItems[1].Author.Id); + Assert.Equal("Redmine 1.1.2 bug/security fix released", newsItems[1].Title); + Assert.Equal("Redmine 1.1.2 has been released", newsItems[1].Description); + Assert.Equal(new DateTime(2011, 3, 7, 20, 7, 3, DateTimeKind.Utc).ToLocalTime(), newsItems[1].CreatedOn); + + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/ProjectTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/ProjectTests.cs new file mode 100644 index 00000000..fd0072c7 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/ProjectTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class ProjectTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Project() + { + const string input = """ + + + 1 + Redmine + redmine + + Redmine is a flexible project management web application written using Ruby on Rails framework. + + + 1 + + + + 2007-09-29T12:03:04+02:00 + 2009-03-15T12:35:11+01:00 + true + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + + Assert.Equal(1, output.Id); + Assert.Equal("Redmine", output.Name); + Assert.Equal("redmine", output.Identifier); + Assert.Contains("Redmine is a flexible project management web application", output.Description); + Assert.Equal(new DateTime(2007, 9, 29, 10, 3, 4, DateTimeKind.Utc).ToLocalTime(), output.CreatedOn); + Assert.Equal(new DateTime(2009, 3, 15, 11, 35, 11, DateTimeKind.Utc).ToLocalTime(), output.UpdatedOn); + Assert.True(output.IsPublic); + } + + [Fact] + public void Should_Deserialize_Projects() + { + const string input = """ + + + + 1 + Redmine + redmine + + Redmine is a flexible project management web application written using Ruby on Rails framework. + + 2007-09-29T12:03:04+02:00 + 2009-03-15T12:35:11+01:00 + true + + + 2 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var projects = output.Items.ToList(); + Assert.Equal(1, projects[0].Id); + Assert.Equal("Redmine", projects[0].Name); + Assert.Equal("redmine", projects[0].Identifier); + Assert.Contains("Redmine is a flexible project management web application", projects[0].Description); + Assert.Equal(new DateTime(2007, 9, 29, 10, 3, 4, DateTimeKind.Utc).ToLocalTime(), projects[0].CreatedOn); + Assert.Equal(new DateTime(2009, 3, 15, 11, 35, 11, DateTimeKind.Utc).ToLocalTime(), projects[0].UpdatedOn); + Assert.True(projects[0].IsPublic); + + Assert.Equal(2, projects[1].Id); + } +} diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/QueryTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/QueryTests.cs new file mode 100644 index 00000000..5927739a --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/QueryTests.cs @@ -0,0 +1,51 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class QueryTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Version() + { + const string input = """ + + + + 84 + Documentation issues + true + 1 + + + 1 + Open defects + true + 1 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(5, output.TotalItems); + + var queries = output.Items.ToList(); + Assert.Equal(2, queries.Count); + + Assert.Equal(84, queries[0].Id); + Assert.Equal("Documentation issues", queries[0].Name); + Assert.True(queries[0].IsPublic); + Assert.Equal(1, queries[0].ProjectId); + + Assert.Equal(1, queries[1].Id); + Assert.Equal("Open defects", queries[1].Name); + Assert.True(queries[1].IsPublic); + Assert.Equal(1, queries[1].ProjectId); + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/RelationTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/RelationTests.cs new file mode 100644 index 00000000..f06be8ac --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/RelationTests.cs @@ -0,0 +1,81 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class RelationTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Relation() + { + const string input = """ + + + 1819 + 8470 + 8469 + relates + + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(1819, output.Id); + Assert.Equal(8470, output.IssueId); + Assert.Equal(8469, output.IssueToId); + Assert.Equal(IssueRelationType.Relates, output.Type); + Assert.Null(output.Delay); + } + + [Fact] + public void Should_Deserialize_Relations() + { + const string input = """ + + + + 1819 + 8470 + 8469 + relates + + + + 1820 + 8470 + 8467 + relates + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var relations = output.Items.ToList(); + Assert.Equal(2, relations.Count); + + Assert.Equal(1819, relations[0].Id); + Assert.Equal(8470, relations[0].IssueId); + Assert.Equal(8469, relations[0].IssueToId); + Assert.Equal(IssueRelationType.Relates, relations[0].Type); + Assert.Null(relations[0].Delay); + + Assert.Equal(1820, relations[1].Id); + Assert.Equal(8470, relations[1].IssueId); + Assert.Equal(8467, relations[1].IssueToId); + Assert.Equal(IssueRelationType.Relates, relations[1].Type); + Assert.Null(relations[1].Delay); + } +} + + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/RoleTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/RoleTests.cs new file mode 100644 index 00000000..7d695ef5 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/RoleTests.cs @@ -0,0 +1,95 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public sealed class RoleTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Role() + { + const string input = """ + + 5 + Reporter + true + default + all + all + + """; + var role = fixture.Serializer.Deserialize(input); + + Assert.Equal(5, role.Id); + Assert.Equal("Reporter", role.Name); + Assert.True(role.IsAssignable); + Assert.Equal("default", role.IssuesVisibility); + Assert.Equal("all", role.TimeEntriesVisibility); + Assert.Equal("all", role.UsersVisibility); + } + + [Fact] + public void Should_Deserialize_Role_And_Permissions() + { + const string input = """ + + 5 + Reporter + true + default + all + all + + view_issues + add_issues + add_issue_notes + + + """; + var role = fixture.Serializer.Deserialize(input); + + Assert.Equal(5, role.Id); + Assert.Equal("Reporter", role.Name); + Assert.True(role.IsAssignable); + Assert.Equal("default", role.IssuesVisibility); + Assert.Equal("all", role.TimeEntriesVisibility); + Assert.Equal("all", role.UsersVisibility); + Assert.Equal(3, role.Permissions.Count); + Assert.Equal("view_issues", role.Permissions[0].Info); + Assert.Equal("add_issues", role.Permissions[1].Info); + Assert.Equal("add_issue_notes", role.Permissions[2].Info); + } + + [Fact] + public void Should_Deserialize_Roles() + { + const string input = """ + + + + 1 + Manager + + + 2 + Developer + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var roles = output.Items.ToList(); + Assert.Equal(1, roles[0].Id); + Assert.Equal("Manager", roles[0].Name); + + Assert.Equal(2, roles[1].Id); + Assert.Equal("Developer", roles[1].Name); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/SearchTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/SearchTests.cs new file mode 100644 index 00000000..5c60001f --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/SearchTests.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public sealed class SearchTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Search_Result() + { + const string input = """ + + + 5 + Wiki: Wiki_Page_Name + wiki-page + http://www.redmine.org/projects/new_crm_dev/wiki/Wiki_Page_Name + h1. Wiki Page Name wiki_keyword + 2016-03-25T05:23:35Z + + + 10 + Issue #10 (Closed): Issue_Title + issue closed + http://www.redmin.org/issues/10 + issue_keyword + 2016-03-24T05:18:59Z + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + Assert.Equal(25, output.PageSize); + + var results = output.Items.ToList(); + Assert.Equal(5, results[0].Id); + Assert.Equal("Wiki: Wiki_Page_Name", results[0].Title); + Assert.Equal("wiki-page", results[0].Type); + Assert.Equal("http://www.redmine.org/projects/new_crm_dev/wiki/Wiki_Page_Name", results[0].Url); + Assert.Equal("h1. Wiki Page Name wiki_keyword", results[0].Description); + Assert.Equal(new DateTime(2016, 3, 25, 5, 23, 35, DateTimeKind.Utc).ToLocalTime(), results[0].DateTime); + + Assert.Equal(10, results[1].Id); + Assert.Equal("Issue #10 (Closed): Issue_Title", results[1].Title); + Assert.Equal("issue closed", results[1].Type); + Assert.Equal("http://www.redmin.org/issues/10", results[1].Url); + Assert.Equal("issue_keyword", results[1].Description); + Assert.Equal(new DateTime(2016, 3, 24, 5, 18, 59, DateTimeKind.Utc).ToLocalTime(), results[1].DateTime); + + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/TrackerTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/TrackerTests.cs new file mode 100644 index 00000000..a61410cb --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/TrackerTests.cs @@ -0,0 +1,134 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class TrackerTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Tracker() + { + const string input = """ + + + 1 + Defect + + Description for Bug tracker + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + + Assert.Equal(1, output.Id); + Assert.Equal("Defect", output.Name); + Assert.Equal("New", output.DefaultStatus.Name); + Assert.Equal("Description for Bug tracker", output.Description); + } + + [Fact] + public void Should_Deserialize_Tracker_With_Enumerations() + { + const string input = """ + + + 1 + Defect + + Description for Bug tracker + + assigned_to_id + category_id + fixed_version_id + parent_issue_id + start_date + due_date + estimated_hours + done_ratio + description + + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + + Assert.Equal(1, output.Id); + Assert.Equal("Defect", output.Name); + Assert.Equal("New", output.DefaultStatus.Name); + Assert.Equal("Description for Bug tracker", output.Description); + Assert.Equal(9, output.EnabledStandardFields.Count); + } + + [Fact] + public void Should_Deserialize_Trackers() + { + const string input = """ + + + + 1 + Defect + + Description for Bug tracker + + assigned_to_id + category_id + fixed_version_id + parent_issue_id + start_date + due_date + estimated_hours + done_ratio + description + + + + 2 + Feature + + Description for Feature request tracker + + assigned_to_id + category_id + fixed_version_id + parent_issue_id + start_date + due_date + estimated_hours + done_ratio + description + + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var trackers = output.Items.ToList(); + Assert.Equal(2, trackers.Count); + + Assert.Equal(1, trackers[0].Id); + Assert.Equal("Defect", trackers[0].Name); + Assert.Equal("New", trackers[0].DefaultStatus.Name); + Assert.Equal("Description for Bug tracker", trackers[0].Description); + Assert.Equal(9, trackers[0].EnabledStandardFields.Count); + + Assert.Equal(2, trackers[1].Id); + Assert.Equal("Feature", trackers[1].Name); + Assert.Equal("New", trackers[1].DefaultStatus.Name); + Assert.Equal("Description for Feature request tracker", trackers[1].Description); + Assert.Equal(9, trackers[1].EnabledStandardFields.Count); + + } +} diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/UploadTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/UploadTests.cs new file mode 100644 index 00000000..ff191c89 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/UploadTests.cs @@ -0,0 +1,62 @@ +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class UploadTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Upload() + { + const string input = """ + + + #{token1} + test1.txt + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal("#{token1}", output.Token); + Assert.Equal("test1.txt", output.FileName); + } + + [Fact] + public void Should_Deserialize_Uploads() + { + const string input = """ + + + + #{token1} + test1.txt + + + #{token2} + test1.txt + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(2, output.TotalItems); + + var uploads = output.Items.ToList(); + Assert.Equal(2, uploads.Count); + + Assert.Equal("#{token1}", uploads[0].Token); + Assert.Equal("test1.txt", uploads[0].FileName); + + Assert.Equal("#{token2}", uploads[1].Token); + Assert.Equal("test1.txt", uploads[1].FileName); + + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/UserTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/UserTests.cs new file mode 100644 index 00000000..e24b0a57 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/UserTests.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class UserTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_User() + { + const string input = """ + + + 3 + jplang + Jean-Philippe + Lang + jp_lang@yahoo.fr + 2007-09-28T00:16:04+02:00 + 2010-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d + + 1 + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(3, output.Id); + Assert.Equal("jplang", output.Login); + Assert.Equal("Jean-Philippe", output.FirstName); + Assert.Equal("Lang", output.LastName); + Assert.Equal("jp_lang@yahoo.fr", output.Email); + Assert.Equal(new DateTime(2007, 9, 28, 0, 16, 4, DateTimeKind.Local).AddHours(1), output.CreatedOn); + Assert.Equal(new DateTime(2010, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.UpdatedOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.LastLoginOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.PasswordChangedOn); + Assert.Equal("ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d", output.ApiKey); + Assert.Empty(output.AvatarUrl); + Assert.Equal(UserStatus.StatusActive, output.Status); + } + + [Fact] + public void Should_Deserialize_User_With_Memberships() + { + const string input = """ + + + 3 + jplang + Jean-Philippe + Lang + jp_lang@yahoo.fr + 2007-09-28T00:16:04+02:00 + 2010-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d + + 1 + + + + + + + + + + + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(3, output.Id); + Assert.Equal("jplang", output.Login); + Assert.Equal("Jean-Philippe", output.FirstName); + Assert.Equal("Lang", output.LastName); + Assert.Equal("jp_lang@yahoo.fr", output.Email); + Assert.Equal(new DateTime(2007, 9, 28, 0, 16, 4, DateTimeKind.Local).AddHours(1), output.CreatedOn); + Assert.Equal(new DateTime(2010, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.UpdatedOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.LastLoginOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.PasswordChangedOn); + Assert.Equal("ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d", output.ApiKey); + Assert.Empty(output.AvatarUrl); + Assert.Equal(UserStatus.StatusActive, output.Status); + + var memberships = output.Memberships.ToList(); + Assert.Single(memberships); + Assert.Equal("Redmine", memberships[0].Project.Name); + Assert.Equal(1, memberships[0].Project.Id); + + var roles = memberships[0].Roles.ToList(); + Assert.Equal(2, roles.Count); + Assert.Equal("Administrator", roles[0].Name); + Assert.Equal(3, roles[0].Id); + Assert.Equal("Contributor", roles[1].Name); + Assert.Equal(4, roles[1].Id); + } + + [Fact] + public void Should_Deserialize_User_With_Groups() + { + const string input = """ + + + 3 + jplang + Jean-Philippe + Lang + jp_lang@yahoo.fr + 2007-09-28T00:16:04+02:00 + 2010-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + 2011-08-01T18:05:45+02:00 + ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d + + 1 + + + + + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(3, output.Id); + Assert.Equal("jplang", output.Login); + Assert.Equal("Jean-Philippe", output.FirstName); + Assert.Equal("Lang", output.LastName); + Assert.Equal("jp_lang@yahoo.fr", output.Email); + Assert.Equal(new DateTime(2007, 9, 28, 0, 16, 4, DateTimeKind.Local).AddHours(1), output.CreatedOn); + Assert.Equal(new DateTime(2010, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.UpdatedOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.LastLoginOn); + Assert.Equal(new DateTime(2011, 8, 1, 18, 5, 45, DateTimeKind.Local).AddHours(1), output.PasswordChangedOn); + Assert.Equal("ebc3f6b781a6fb3f2b0a83ce0ebb80e0d585189d", output.ApiKey); + Assert.Empty(output.AvatarUrl); + Assert.Equal(UserStatus.StatusActive, output.Status); + + var groups = output.Groups.ToList(); + Assert.Single(groups); + Assert.Equal("Developers", groups[0].Name); + Assert.Equal(20, groups[0].Id); + } +} + diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/VersionTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/VersionTests.cs new file mode 100644 index 00000000..860e1a93 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/VersionTests.cs @@ -0,0 +1,111 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class VersionTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Version() + { + const string input = """ + + + 2 + + 0.8 + + closed + 2008-12-30 + 0.0 + 0.0 + 2008-03-09T12:52:12+01:00 + 2009-11-15T12:22:12+01:00 + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal(2, output.Id); + Assert.Equal("Redmine", output.Project.Name); + Assert.Equal(1, output.Project.Id); + Assert.Equal("0.8", output.Name); + Assert.Equal(VersionStatus.Closed, output.Status); + Assert.Equal(new DateTime(2008, 12, 30), output.DueDate); + Assert.Equal(0.0f, output.EstimatedHours); + Assert.Equal(0.0f, output.SpentHours); + Assert.Equal(new DateTime(2008, 3, 9, 12, 52, 12, DateTimeKind.Local).AddHours(1), output.CreatedOn); + Assert.Equal(new DateTime(2009, 11, 15, 12, 22, 12, DateTimeKind.Local).AddHours(1), output.UpdatedOn); + + } + + [Fact] + public void Should_Deserialize_Versions() + { + const string input = """ + + + + 1 + + 0.7 + + closed + 2008-04-28 + none + 2008-03-09T12:52:06+01:00 + 2009-11-15T12:22:12+01:00 + FooBarWikiPage + + + 2 + + 0.8 + + closed + 2008-12-30 + none + FooBarWikiPage + 2008-03-09T12:52:12+01:00 + 2009-11-15T12:22:12+01:00 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(34, output.TotalItems); + + var versions = output.Items.ToList(); + Assert.Equal(1, versions[0].Id); + Assert.Equal("Redmine", versions[0].Project.Name); + Assert.Equal(1, versions[0].Project.Id); + Assert.Equal("0.7", versions[0].Name); + Assert.Equal(VersionStatus.Closed, versions[0].Status); + Assert.Equal(new DateTime(2008, 4, 28), versions[0].DueDate); + Assert.Equal(VersionSharing.None, versions[0].Sharing); + Assert.Equal("FooBarWikiPage", versions[0].WikiPageTitle); + Assert.Equal(new DateTime(2008, 3, 9, 12, 52, 6, DateTimeKind.Local).AddHours(1), versions[0].CreatedOn); + Assert.Equal(new DateTime(2009, 11, 15, 12, 22, 12, DateTimeKind.Local).AddHours(1), versions[0].UpdatedOn); + + Assert.Equal(2, versions[1].Id); + Assert.Equal("Redmine", versions[1].Project.Name); + Assert.Equal(1, versions[1].Project.Id); + Assert.Equal("0.8", versions[1].Name); + Assert.Equal(VersionStatus.Closed, versions[1].Status); + Assert.Equal(new DateTime(2008, 12, 30), versions[1].DueDate); + Assert.Equal(VersionSharing.None, versions[1].Sharing); + Assert.Equal(new DateTime(2008, 3, 9, 12, 52, 12, DateTimeKind.Local).AddHours(1), versions[1].CreatedOn); + Assert.Equal(new DateTime(2009, 11, 15, 12, 22, 12, DateTimeKind.Local).AddHours(1), versions[1].UpdatedOn); + + } +} + + \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Xml/WikiTests.cs b/tests/redmine-net-api.Tests/Serialization/Xml/WikiTests.cs new file mode 100644 index 00000000..768ffde0 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Xml/WikiTests.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Xml; + +[Collection(Constants.XmlRedmineSerializerCollection)] +public class WikiTests(XmlSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Wiki_Page() + { + const string input = """ + + + UsersGuide + + h1. Users Guide + ... + ... + 22 + + Typo + 2009-05-18T20:11:52Z + 2012-10-02T11:38:18Z + + """; + + var output = fixture.Serializer.Deserialize(input); + + Assert.NotNull(output); + Assert.Equal("UsersGuide", output.Title); + Assert.NotNull(output.ParentTitle); + Assert.Equal("Installation_Guide", output.ParentTitle); + + Assert.NotNull(output.Text); + Assert.False(string.IsNullOrWhiteSpace(output.Text), "Text should not be empty"); + + var lines = output.Text!.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + var firstLine = lines[0].Trim(); + + Assert.Equal("h1. Users Guide", firstLine); + + Assert.Equal(22, output.Version); + Assert.NotNull(output.Author); + Assert.Equal(11, output.Author.Id); + Assert.Equal("John Smith", output.Author.Name); + Assert.Equal("Typo", output.Comments); + Assert.Equal(new DateTime(2009, 5, 18, 20, 11, 52, DateTimeKind.Utc).ToLocalTime(), output.CreatedOn); + Assert.Equal(new DateTime(2012, 10, 2, 11, 38, 18, DateTimeKind.Utc).ToLocalTime(), output.UpdatedOn); + + } + + [Fact] + public void Should_Deserialize_Wiki_Pages() + { + const string input = """ + + + + UsersGuide + 2 + 2008-03-09T12:07:08Z + 2008-03-09T23:41:33+01:00 + + + """; + + var output = fixture.Serializer.DeserializeToPagedResults(input); + + Assert.NotNull(output); + Assert.Equal(1, output.TotalItems); + + var wikiPages = output.Items.ToList(); + Assert.Equal("UsersGuide", wikiPages[0].Title); + Assert.Equal(2, wikiPages[0].Version); + Assert.Equal(new DateTime(2008, 3, 9, 12, 7, 8, DateTimeKind.Utc).ToLocalTime(), wikiPages[0].CreatedOn); + Assert.Equal(new DateTime(2008, 3, 9, 22, 41, 33, DateTimeKind.Utc).ToLocalTime(), wikiPages[0].UpdatedOn); + } +} + + + + \ No newline at end of file From fa5577756d0d775a1e9ee09cd0c125eefa6ec89d Mon Sep 17 00:00:00 2001 From: zapadi Date: Mon, 27 Jan 2025 23:08:37 +0200 Subject: [PATCH 09/14] [Tests] Json deserializer tests --- .../Serialization/Json/MyAccount.cs | 56 +++++++++++++++++++ .../Serialization/Json/RoleTests.cs | 45 +++++++++++++++ 2 files changed, 101 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Serialization/Json/MyAccount.cs create mode 100644 tests/redmine-net-api.Tests/Serialization/Json/RoleTests.cs diff --git a/tests/redmine-net-api.Tests/Serialization/Json/MyAccount.cs b/tests/redmine-net-api.Tests/Serialization/Json/MyAccount.cs new file mode 100644 index 00000000..9aa09515 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Json/MyAccount.cs @@ -0,0 +1,56 @@ +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; + +[Collection(Constants.JsonRedmineSerializerCollection)] +public class MyAccount(JsonSerializerFixture fixture) +{ + [Fact] + public void Test_Xml_Serialization() + { + const string input = """ + { + "user": { + "id": 3, + "login": "dlopper", + "admin": false, + "firstname": "Dave", + "lastname": "Lopper", + "mail": "dlopper@somenet.foo", + "created_on": "2006-07-19T17:33:19Z", + "last_login_on": "2020-06-14T13:03:34Z", + "api_key": "c308a59c9dea95920b13522fb3e0fb7fae4f292d", + "custom_fields": [ + { + "id": 4, + "name": "Phone number", + "value": null + }, + { + "id": 5, + "name": "Money", + "value": null + } + ] + } + } + """; + + var output = fixture.Serializer.Deserialize(input); + Assert.Equal(3, output.Id); + Assert.Equal("dlopper", output.Login); + Assert.False(output.IsAdmin); + Assert.Equal("Dave", output.FirstName); + Assert.Equal("Lopper", output.LastName); + Assert.Equal("dlopper@somenet.foo", output.Email); + Assert.Equal("c308a59c9dea95920b13522fb3e0fb7fae4f292d", output.ApiKey); + Assert.NotNull(output.CustomFields); + Assert.Equal(2, output.CustomFields.Count); + Assert.Equal("Phone number", output.CustomFields[0].Name); + Assert.Equal(4, output.CustomFields[0].Id); + Assert.Equal("Money", output.CustomFields[1].Name); + Assert.Equal(5, output.CustomFields[1].Id); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Serialization/Json/RoleTests.cs b/tests/redmine-net-api.Tests/Serialization/Json/RoleTests.cs new file mode 100644 index 00000000..2434ea18 --- /dev/null +++ b/tests/redmine-net-api.Tests/Serialization/Json/RoleTests.cs @@ -0,0 +1,45 @@ +using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Infrastructure.Fixtures; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Serialization.Json; + +[Collection(Constants.JsonRedmineSerializerCollection)] +public sealed class RoleTests(JsonSerializerFixture fixture) +{ + [Fact] + public void Should_Deserialize_Role_And_Permissions() + { + const string input = """ + { + "role": { + "id": 5, + "name": "Reporter", + "assignable": true, + "issues_visibility": "default", + "time_entries_visibility": "all", + "users_visibility": "all", + "permissions": [ + "view_issues", + "add_issues", + "add_issue_notes", + ] + } + } + """; + + var role = fixture.Serializer.Deserialize(input); + + Assert.Equal(5, role.Id); + Assert.Equal("Reporter", role.Name); + Assert.True(role.IsAssignable); + Assert.Equal("default", role.IssuesVisibility); + Assert.Equal("all", role.TimeEntriesVisibility); + Assert.Equal("all", role.UsersVisibility); + Assert.Equal(3, role.Permissions.Count); + Assert.Equal("view_issues", role.Permissions[0].Info); + Assert.Equal("add_issues", role.Permissions[1].Info); + Assert.Equal("add_issue_notes", role.Permissions[2].Info); + } +} \ No newline at end of file From 7b9cd707267ae04210c9c99c81c03a758ea641e3 Mon Sep 17 00:00:00 2001 From: zapadi Date: Wed, 12 Mar 2025 22:38:48 +0200 Subject: [PATCH 10/14] [Tests ] Add clonable tests --- .../Clone/AttachmentCloneTests.cs | 82 +++++++++++ .../Clone/IssueCloneTests.cs | 129 ++++++++++++++++++ .../Clone/JournalCloneTests.cs | 60 ++++++++ 3 files changed, 271 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Clone/AttachmentCloneTests.cs create mode 100644 tests/redmine-net-api.Tests/Clone/IssueCloneTests.cs create mode 100644 tests/redmine-net-api.Tests/Clone/JournalCloneTests.cs diff --git a/tests/redmine-net-api.Tests/Clone/AttachmentCloneTests.cs b/tests/redmine-net-api.Tests/Clone/AttachmentCloneTests.cs new file mode 100644 index 00000000..dc8a2830 --- /dev/null +++ b/tests/redmine-net-api.Tests/Clone/AttachmentCloneTests.cs @@ -0,0 +1,82 @@ +using System; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Clone; + +public sealed class AttachmentCloneTests +{ + [Fact] + public void Clone_WithPopulatedProperties_ReturnsDeepCopy() + { + // Arrange + var attachment = new Attachment + { + Id = 1, + FileName = "test.txt", + FileSize = 1024, + ContentType = "text/plain", + Description = "Test file", + ContentUrl = "http://example.com/test.txt", + ThumbnailUrl = "http://example.com/thumb.txt", + Author = new IdentifiableName(1, "John Doe"), + CreatedOn = DateTime.Now + }; + + // Act + var clone = attachment.Clone(false); + + // Assert + Assert.NotNull(clone); + Assert.NotSame(attachment, clone); + Assert.Equal(attachment.Id, clone.Id); + Assert.Equal(attachment.FileName, clone.FileName); + Assert.Equal(attachment.FileSize, clone.FileSize); + Assert.Equal(attachment.ContentType, clone.ContentType); + Assert.Equal(attachment.Description, clone.Description); + Assert.Equal(attachment.ContentUrl, clone.ContentUrl); + Assert.Equal(attachment.ThumbnailUrl, clone.ThumbnailUrl); + Assert.Equal(attachment.CreatedOn, clone.CreatedOn); + + Assert.NotSame(attachment.Author, clone.Author); + Assert.Equal(attachment.Author.Id, clone.Author.Id); + Assert.Equal(attachment.Author.Name, clone.Author.Name); + } + + [Fact] + public void Clone_With_ResetId_True_Should_Return_A_Copy_With_Id_Set_Zero() + { + // Arrange + var attachment = new Attachment + { + Id = 1, + FileName = "test.txt", + FileSize = 1024, + ContentType = "text/plain", + Description = "Test file", + ContentUrl = "http://example.com/test.txt", + ThumbnailUrl = "http://example.com/thumb.txt", + Author = new IdentifiableName(1, "John Doe"), + CreatedOn = DateTime.Now + }; + + // Act + var clone = attachment.Clone(true); + + // Assert + Assert.NotNull(clone); + Assert.NotSame(attachment, clone); + Assert.NotEqual(attachment.Id, clone.Id); + Assert.Equal(attachment.FileName, clone.FileName); + Assert.Equal(attachment.FileSize, clone.FileSize); + Assert.Equal(attachment.ContentType, clone.ContentType); + Assert.Equal(attachment.Description, clone.Description); + Assert.Equal(attachment.ContentUrl, clone.ContentUrl); + Assert.Equal(attachment.ThumbnailUrl, clone.ThumbnailUrl); + Assert.Equal(attachment.CreatedOn, clone.CreatedOn); + + Assert.NotSame(attachment.Author, clone.Author); + Assert.Equal(attachment.Author.Id, clone.Author.Id); + Assert.Equal(attachment.Author.Name, clone.Author.Name); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Clone/IssueCloneTests.cs b/tests/redmine-net-api.Tests/Clone/IssueCloneTests.cs new file mode 100644 index 00000000..3aac1e6c --- /dev/null +++ b/tests/redmine-net-api.Tests/Clone/IssueCloneTests.cs @@ -0,0 +1,129 @@ +using System; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Clone; + +public sealed class IssueCloneTests +{ + [Fact] + public void Clone_WithNullProperties_ReturnsNewInstanceWithNullProperties() + { + // Arrange + var issue = new Issue(); + + // Act + var clone = issue.Clone(true); + + // Assert + Assert.NotNull(clone); + Assert.NotSame(issue, clone); + Assert.Equal(issue.Id, clone.Id); + Assert.Null(clone.Project); + Assert.Null(clone.Tracker); + Assert.Null(clone.Status); + } + + [Fact] + public void Clone_WithPopulatedProperties_ReturnsDeepCopy() + { + // Arrange + var issue = CreateSampleIssue(); + + // Act + var clone = issue.Clone(true); + + // Assert + Assert.NotNull(clone); + Assert.NotSame(issue, clone); + + Assert.NotEqual(issue.Id, clone.Id); + Assert.Equal(issue.Subject, clone.Subject); + Assert.Equal(issue.Description, clone.Description); + Assert.Equal(issue.DoneRatio, clone.DoneRatio); + Assert.Equal(issue.IsPrivate, clone.IsPrivate); + Assert.Equal(issue.EstimatedHours, clone.EstimatedHours); + Assert.Equal(issue.CreatedOn, clone.CreatedOn); + Assert.Equal(issue.UpdatedOn, clone.UpdatedOn); + Assert.Equal(issue.ClosedOn, clone.ClosedOn); + + Assert.NotSame(issue.Project, clone.Project); + Assert.Equal(issue.Project.Id, clone.Project.Id); + Assert.Equal(issue.Project.Name, clone.Project.Name); + + Assert.NotSame(issue.Tracker, clone.Tracker); + Assert.Equal(issue.Tracker.Id, clone.Tracker.Id); + Assert.Equal(issue.Tracker.Name, clone.Tracker.Name); + + Assert.NotSame(issue.CustomFields, clone.CustomFields); + Assert.Equal(issue.CustomFields.Count, clone.CustomFields.Count); + for (var i = 0; i < issue.CustomFields.Count; i++) + { + Assert.NotSame(issue.CustomFields[i], clone.CustomFields[i]); + Assert.Equal(issue.CustomFields[i].Id, clone.CustomFields[i].Id); + Assert.Equal(issue.CustomFields[i].Name, clone.CustomFields[i].Name); + } + + Assert.NotNull(clone.Attachments); + Assert.Equal(issue.Attachments.Count, clone.Attachments.Count); + Assert.All(clone.Attachments, Assert.NotNull); + } + + [Fact] + public void Clone_ModifyingClone_DoesNotAffectOriginal() + { + // Arrange + var issue = CreateSampleIssue(); + var clone = issue.Clone(true); + + // Act + clone.Subject = "Modified Subject"; + clone.Project.Name = "Modified Project"; + clone.CustomFields[0].Values = [new CustomFieldValue("Modified Value")]; + + // Assert + Assert.NotEqual(issue.Subject, clone.Subject); + Assert.NotEqual(issue.Project.Name, clone.Project.Name); + Assert.NotEqual(issue.CustomFields[0].Values, clone.CustomFields[0].Values); + } + + private static Issue CreateSampleIssue() + { + return new Issue + { + Id = 1, + Project = new IdentifiableName(100, "Test Project"), + Tracker = new IdentifiableName(200, "Bug"), + Status = new IdentifiableName(300, "New"), + Priority = new IdentifiableName(400, "Normal"), + Author = new IdentifiableName(500, "John Doe"), + Subject = "Test Issue", + Description = "Test Description", + StartDate = DateTime.Today, + DueDate = DateTime.Today.AddDays(7), + DoneRatio = 50, + IsPrivate = false, + EstimatedHours = 8.5f, + CreatedOn = DateTime.Now.AddDays(-1), + UpdatedOn = DateTime.Now, + CustomFields = + [ + new IssueCustomField + { + Id = 1, + Name = "Custom Field 1", + } + ], + Attachments = + [ + new Attachment + { + Id = 1, + FileName = "test.txt", + FileSize = 1024, + Author = new IdentifiableName(1, "Author") + } + ] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Clone/JournalCloneTests.cs b/tests/redmine-net-api.Tests/Clone/JournalCloneTests.cs new file mode 100644 index 00000000..37440f6e --- /dev/null +++ b/tests/redmine-net-api.Tests/Clone/JournalCloneTests.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Clone; + +public sealed class JournalCloneTests +{ + [Fact] + public void Clone_WithPopulatedProperties_ReturnsDeepCopy() + { + // Arrange + var journal = new Journal + { + Id = 1, + User = new IdentifiableName(1, "John Doe"), + Notes = "Test notes", + CreatedOn = DateTime.Now, + PrivateNotes = true, + Details = (List) + [ + new Detail + { + Property = "status_id", + Name = "Status", + OldValue = "1", + NewValue = "2" + } + ] + }; + + // Act + var clone = journal.Clone(false); + + // Assert + Assert.NotNull(clone); + Assert.NotSame(journal, clone); + Assert.Equal(journal.Id, clone.Id); + Assert.Equal(journal.Notes, clone.Notes); + Assert.Equal(journal.CreatedOn, clone.CreatedOn); + Assert.Equal(journal.PrivateNotes, clone.PrivateNotes); + + Assert.NotSame(journal.User, clone.User); + Assert.Equal(journal.User.Id, clone.User.Id); + Assert.Equal(journal.User.Name, clone.User.Name); + + Assert.NotNull(clone.Details); + Assert.NotSame(journal.Details, clone.Details); + Assert.Equal(journal.Details.Count, clone.Details.Count); + + var originalDetail = journal.Details[0]; + var clonedDetail = clone.Details[0]; + Assert.NotSame(originalDetail, clonedDetail); + Assert.Equal(originalDetail.Property, clonedDetail.Property); + Assert.Equal(originalDetail.Name, clonedDetail.Name); + Assert.Equal(originalDetail.OldValue, clonedDetail.OldValue); + Assert.Equal(originalDetail.NewValue, clonedDetail.NewValue); + } +} \ No newline at end of file From d1e0173430f5e604b47c375ce21de0429c731c78 Mon Sep 17 00:00:00 2001 From: zapadi Date: Wed, 12 Mar 2025 22:38:35 +0200 Subject: [PATCH 11/14] [#229] Add tests --- .../Bugs/RedmineApi-229.cs | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Bugs/RedmineApi-229.cs diff --git a/tests/redmine-net-api.Tests/Bugs/RedmineApi-229.cs b/tests/redmine-net-api.Tests/Bugs/RedmineApi-229.cs new file mode 100644 index 00000000..64ae736a --- /dev/null +++ b/tests/redmine-net-api.Tests/Bugs/RedmineApi-229.cs @@ -0,0 +1,125 @@ +using System; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Bugs; + +public sealed class RedmineApi229 +{ + [Fact] + public void Equals_ShouldReturnTrue_WhenComparingWithSelf() + { + // Arrange + var timeEntry = CreateSampleTimeEntry(); + + // Act & Assert + Assert.True(timeEntry.Equals(timeEntry), "TimeEntry should equal itself (reference equality)"); + Assert.True(timeEntry == timeEntry, "TimeEntry should equal itself using == operator"); + Assert.True(timeEntry.Equals((object)timeEntry), "TimeEntry should equal itself when cast to object"); + Assert.Equal(timeEntry.GetHashCode(), timeEntry.GetHashCode()); + } + + [Fact] + public void Equals_ShouldReturnTrue_WhenComparingIdenticalInstances() + { + // Arrange + var timeEntry1 = CreateSampleTimeEntry(); + var timeEntry2 = CreateSampleTimeEntry(); + + // Act & Assert + Assert.True(timeEntry1.Equals(timeEntry2), "Identical TimeEntry instances should be equal"); + Assert.True(timeEntry2.Equals(timeEntry1), "Equality should be symmetric"); + Assert.Equal(timeEntry1.GetHashCode(), timeEntry2.GetHashCode()); + } + + [Fact] + public void Equals_ShouldReturnFalse_WhenComparingWithNull() + { + // Arrange + var timeEntry = CreateSampleTimeEntry(); + + // Act & Assert + Assert.False(timeEntry.Equals(null)); + Assert.False(timeEntry.Equals((object)null)); + } + + [Fact] + public void Equals_ShouldReturnFalse_WhenComparingDifferentTypes() + { + // Arrange + var timeEntry = CreateSampleTimeEntry(); + var differentObject = new object(); + + // Act & Assert + Assert.False(timeEntry.Equals(differentObject)); + } + + [Theory] + [MemberData(nameof(GetDifferentTimeEntries))] + public void Equals_ShouldReturnFalse_WhenPropertiesDiffer(TimeEntry different, string propertyName) + { + // Arrange + var baseline = CreateSampleTimeEntry(); + + // Act & Assert + Assert.False(baseline.Equals(different), $"TimeEntries should not be equal when {propertyName} differs"); + } + + private static TimeEntry CreateSampleTimeEntry() => new() + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project" }, + Issue = new IdentifiableName { Id = 1, Name = "Issue" }, + User = new IdentifiableName { Id = 1, Name = "User" }, + Activity = new IdentifiableName { Id = 1, Name = "Activity" }, + Hours = (decimal)8.0, + Comments = "Test comment", + SpentOn = new DateTime(2023, 1, 1), + CreatedOn = new DateTime(2023, 1, 1), + UpdatedOn = new DateTime(2023, 1, 1), + CustomFields = + [ + new() { Id = 1, Name = "Field1"} + ] + }; + + public static TheoryData GetDifferentTimeEntries() + { + var data = new TheoryData(); + + // Different ID + var differentId = CreateSampleTimeEntry(); + differentId.Id = 2; + data.Add(differentId, "Id"); + + // Different Project + var differentProject = CreateSampleTimeEntry(); + differentProject.Project = new IdentifiableName { Id = 2, Name = "Different Project" }; + data.Add(differentProject, "Project"); + + // Different Issue + var differentIssue = CreateSampleTimeEntry(); + differentIssue.Issue = new IdentifiableName { Id = 2, Name = "Different Issue" }; + data.Add(differentIssue, "Issue"); + + // Different Hours + var differentHours = CreateSampleTimeEntry(); + differentHours.Hours = (decimal)4.0; + data.Add(differentHours, "Hours"); + + // Different CustomFields + var differentCustomFields = CreateSampleTimeEntry(); + differentCustomFields.CustomFields = + [ + new() { Id = 2, Name = "Field2" } + ]; + data.Add(differentCustomFields, "CustomFields"); + + // Different SpentOn + var differentSpentOn = CreateSampleTimeEntry(); + differentSpentOn.SpentOn = new DateTime(2023, 1, 2); + data.Add(differentSpentOn, "SpentOn"); + + return data; + } +} \ No newline at end of file From 0abee0c439de454d88e3429a0c18c0beffead3e3 Mon Sep 17 00:00:00 2001 From: zapadi Date: Sun, 30 Mar 2025 22:46:53 +0300 Subject: [PATCH 12/14] [Tests] RedmineApiUrls --- .../Fixtures/RedmineApiUrlsFixture.cs | 31 + .../Tests/RedmineApiUrlsTests.cs | 573 ++++++++++++++++++ 2 files changed, 604 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineApiUrlsFixture.cs create mode 100644 tests/redmine-net-api.Tests/Tests/RedmineApiUrlsTests.cs diff --git a/tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineApiUrlsFixture.cs b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineApiUrlsFixture.cs new file mode 100644 index 00000000..7a914dae --- /dev/null +++ b/tests/redmine-net-api.Tests/Infrastructure/Fixtures/RedmineApiUrlsFixture.cs @@ -0,0 +1,31 @@ +using System.Diagnostics; +using Redmine.Net.Api.Net; + +namespace Padi.DotNet.RedmineAPI.Tests.Tests; + +public sealed class RedmineApiUrlsFixture +{ + internal string Format { get; private set; } + + public RedmineApiUrlsFixture() + { + SetMimeTypeJson(); + SetMimeTypeXml(); + + Sut = new RedmineApiUrls(Format); + } + + internal RedmineApiUrls Sut { get; } + + [Conditional("DEBUG_JSON")] + private void SetMimeTypeJson() + { + Format = "json"; + } + + [Conditional("DEBUG_XML")] + private void SetMimeTypeXml() + { + Format = "json"; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Tests/RedmineApiUrlsTests.cs b/tests/redmine-net-api.Tests/Tests/RedmineApiUrlsTests.cs new file mode 100644 index 00000000..8b0121bb --- /dev/null +++ b/tests/redmine-net-api.Tests/Tests/RedmineApiUrlsTests.cs @@ -0,0 +1,573 @@ +using System; +using System.Collections.Specialized; +using Redmine.Net.Api; +using Redmine.Net.Api.Exceptions; +using Redmine.Net.Api.Net; +using Redmine.Net.Api.Types; +using Xunit; +using Version = Redmine.Net.Api.Types.Version; + + +namespace Padi.DotNet.RedmineAPI.Tests.Tests; + +public class RedmineApiUrlsTests(RedmineApiUrlsFixture fixture) : IClassFixture +{ + [Fact] + public void MyAccount_ReturnsCorrectUrl() + { + var result = fixture.Sut.MyAccount(); + Assert.Equal("my/account.json", result); + } + + [Theory] + [MemberData(nameof(ProjectOperationsData))] + public void ProjectOperations_ReturnsCorrectUrl(string projectId, Func operation, string expected) + { + var result = operation(projectId); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(WikiOperationsData))] + public void WikiOperations_ReturnsCorrectUrl(string projectId, string pageName, Func operation, string expected) + { + var result = operation(projectId, pageName); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("123", "456", "issues/123/watchers/456.json")] + public void IssueWatcherRemove_WithValidIds_ReturnsCorrectUrl(string issueId, string userId, string expected) + { + var result = fixture.Sut.IssueWatcherRemove(issueId, userId); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData(null, "456")] + [InlineData("123", null)] + [InlineData("", "456")] + [InlineData("123", "")] + public void IssueWatcherRemove_WithInvalidIds_ThrowsRedmineException(string issueId, string userId) + { + Assert.Throws(() => fixture.Sut.IssueWatcherRemove(issueId, userId)); + } + + [Theory] + [MemberData(nameof(AttachmentOperationsData))] + public void AttachmentOperations_WithValidInput_ReturnsCorrectUrl(string input, Func operation, string expected) + { + var result = operation(input); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("test.txt", "uploads.json?filename=test.txt")] + [InlineData("file with spaces.pdf", "uploads.json?filename=file%20with%20spaces.pdf")] + public void UploadFragment_WithFileName_ReturnsCorrectlyEncodedUrl(string fileName, string expected) + { + var result = fixture.Sut.UploadFragment(fileName); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("project1", "versions")] + [InlineData("project1", "issue_categories")] + public void ProjectParentFragment_ForDifferentTypes_ReturnsCorrectUrl(string projectId, string fragment) + { + var expected = $"projects/{projectId}/{fragment}.json"; + var result = fixture.Sut.ProjectParentFragment(projectId, fragment); + Assert.Equal(expected, result); + } + + [Theory] + [InlineData("issue1", "relations")] + [InlineData("issue1", "watchers")] + public void IssueParentFragment_ForDifferentTypes_ReturnsCorrectUrl(string issueId, string fragment) + { + var expected = $"issues/{issueId}/{fragment}.json"; + var result = fixture.Sut.IssueParentFragment(issueId, fragment); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetFragmentTestData))] + public void GetFragment_ForAllTypes_ReturnsCorrectUrl(Type type, string id, string expected) + { + var result = fixture.Sut.GetFragment(type, id); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(CreateEntityTestData))] + public void CreateEntity_ForAllTypes_ReturnsCorrectUrl(Type type, string ownerId, string expected) + { + var result = fixture.Sut.CreateEntityFragment(type, ownerId); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListTestData))] + public void GetList_ForAllTypes_ReturnsCorrectUrl(Type type, string ownerId, string expected) + { + var result = fixture.Sut.GetListFragment(type, ownerId); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(InvalidTypeTestData))] + public void GetList_WithInvalidType_ThrowRedmineException(Type invalidType) + { + var exception = Assert.Throws(() => fixture.Sut.GetListFragment(invalidType)); + Assert.Contains("There is no uri fragment defined for type", exception.Message); + } + + [Theory] + [MemberData(nameof(GetListWithIssueIdTestData))] + public void GetListFragment_WithIssueIdInRequestOptions_ReturnsCorrectUrl(Type type, string issueId, string expected) + { + var requestOptions = new RequestOptions + { + QueryString = new NameValueCollection + { + { RedmineKeys.ISSUE_ID, issueId } + } + }; + + var result = fixture.Sut.GetListFragment(type, requestOptions); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListWithProjectIdTestData))] + public void GetListFragment_WithProjectIdInRequestOptions_ReturnsCorrectUrl(Type type, string projectId, string expected) + { + var requestOptions = new RequestOptions + { + QueryString = new NameValueCollection + { + { RedmineKeys.PROJECT_ID, projectId } + } + }; + + var result = fixture.Sut.GetListFragment(type, requestOptions); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListWithBothIdsTestData))] + public void GetListFragment_WithBothIds_PrioritizesProjectId(Type type, string projectId, string issueId, string expected) + { + var requestOptions = new RequestOptions + { + QueryString = new NameValueCollection + { + { RedmineKeys.PROJECT_ID, projectId }, + { RedmineKeys.ISSUE_ID, issueId } + } + }; + + var result = fixture.Sut.GetListFragment(type, requestOptions); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListWithNoIdsTestData))] + public void GetListFragment_WithNoIds_ReturnsDefaultUrl(Type type, string expected) + { + var result = fixture.Sut.GetListFragment(type, new RequestOptions()); + Assert.Equal(expected, result); + } + + [Theory] + [ClassData(typeof(RedmineTypeTestData))] + public void GetListFragment_ForAllTypes_ReturnsCorrectUrl(Type type, string parentId, string expected) + { + var result = fixture.Sut.GetListFragment(type, parentId); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListEntityRequestOptionTestData))] + public void GetListFragment_WithEmptyOptions_ReturnsCorrectUrl(Type type, RequestOptions requestOptions, string expected) + { + var result = fixture.Sut.GetListFragment(type, requestOptions); + Assert.Equal(expected, result); + } + + [Theory] + [ClassData(typeof(RedmineTypeTestData))] + public void GetListFragment_WithNullOptions_ReturnsCorrectUrl(Type type, string parentId, string expected) + { + var result = fixture.Sut.GetListFragment(type, parentId); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListWithNullRequestOptionsTestData))] + public void GetListFragment_WithNullRequestOptions_ReturnsDefaultUrl(Type type, string expected) + { + var result = fixture.Sut.GetListFragment(type, (RequestOptions)null); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(GetListWithEmptyQueryStringTestData))] + public void GetListFragment_WithEmptyQueryString_ReturnsDefaultUrl(Type type, string expected) + { + var requestOptions = new RequestOptions + { + QueryString = null + }; + + var result = fixture.Sut.GetListFragment(type, requestOptions); + Assert.Equal(expected, result); + } + + [Fact] + public void GetListFragment_WithCustomQueryParameters_DoesNotAffectUrl() + { + var requestOptions = new RequestOptions + { + QueryString = new NameValueCollection + { + { "status_id", "1" }, + { "assigned_to_id", "me" }, + { "sort", "priority:desc" } + } + }; + + var result = fixture.Sut.GetListFragment(requestOptions); + Assert.Equal("issues.json", result); + } + + [Theory] + [MemberData(nameof(GetListWithInvalidTypeTestData))] + public void GetListFragment_WithInvalidType_ThrowsRedmineException(Type invalidType) + { + var exception = Assert.Throws(() => fixture.Sut.GetListFragment(invalidType)); + + Assert.Contains("There is no uri fragment defined for type", exception.Message); + } + + public static TheoryData GetListWithBothIdsTestData() + { + return new TheoryData + { + { + typeof(Version), + "project1", + "issue1", + "projects/project1/versions.json" + }, + { + typeof(IssueCategory), + "project2", + "issue2", + "projects/project2/issue_categories.json" + } + }; + } + + public class RedmineTypeTestData : TheoryData + { + public RedmineTypeTestData() + { + Add(null, "issues.json"); + Add(null,"projects.json"); + Add(null,"users.json"); + Add(null,"time_entries.json"); + Add(null,"custom_fields.json"); + Add(null,"groups.json"); + Add(null,"news.json"); + Add(null,"queries.json"); + Add(null,"roles.json"); + Add(null,"issue_statuses.json"); + Add(null,"trackers.json"); + Add(null,"enumerations/issue_priorities.json"); + Add(null,"enumerations/time_entry_activities.json"); + Add("1","projects/1/versions.json"); + Add("1","projects/1/issue_categories.json"); + Add("1","projects/1/memberships.json"); + Add("1","issues/1/relations.json"); + Add(null,"attachments.json"); + Add(null,"custom_fields.json"); + Add(null,"journals.json"); + Add(null,"search.json"); + Add(null,"watchers.json"); + } + + private void Add(string parentId, string expected) where T : class, new() + { + AddRow(typeof(T), parentId, expected); + } + } + + public static TheoryData GetFragmentTestData() + { + return new TheoryData + { + { typeof(Attachment), "1", "attachments/1.json" }, + { typeof(CustomField), "2", "custom_fields/2.json" }, + { typeof(Group), "3", "groups/3.json" }, + { typeof(Issue), "4", "issues/4.json" }, + { typeof(IssueCategory), "5", "issue_categories/5.json" }, + { typeof(IssueCustomField), "6", "custom_fields/6.json" }, + { typeof(IssuePriority), "7", "enumerations/issue_priorities/7.json" }, + { typeof(IssueRelation), "8", "relations/8.json" }, + { typeof(IssueStatus), "9", "issue_statuses/9.json" }, + { typeof(Journal), "10", "journals/10.json" }, + { typeof(News), "11", "news/11.json" }, + { typeof(Project), "12", "projects/12.json" }, + { typeof(ProjectMembership), "13", "memberships/13.json" }, + { typeof(Query), "14", "queries/14.json" }, + { typeof(Role), "15", "roles/15.json" }, + { typeof(Search), "16", "search/16.json" }, + { typeof(TimeEntry), "17", "time_entries/17.json" }, + { typeof(TimeEntryActivity), "18", "enumerations/time_entry_activities/18.json" }, + { typeof(Tracker), "19", "trackers/19.json" }, + { typeof(User), "20", "users/20.json" }, + { typeof(Version), "21", "versions/21.json" }, + { typeof(Watcher), "22", "watchers/22.json" } + }; + } + + public static TheoryData CreateEntityTestData() + { + return new TheoryData + { + { typeof(Version), "project1", "projects/project1/versions.json" }, + { typeof(IssueCategory), "project1", "projects/project1/issue_categories.json" }, + { typeof(ProjectMembership), "project1", "projects/project1/memberships.json" }, + + { typeof(IssueRelation), "issue1", "issues/issue1/relations.json" }, + + { typeof(File), "project1", "projects/project1/files.json" }, + { typeof(Upload), null, "uploads.json" }, + { typeof(Attachment), "issue1", "/attachments/issues/issue1.json" }, + + { typeof(Issue), null, "issues.json" }, + { typeof(Project), null, "projects.json" }, + { typeof(User), null, "users.json" }, + { typeof(TimeEntry), null, "time_entries.json" }, + { typeof(News), null, "news.json" }, + { typeof(Query), null, "queries.json" }, + { typeof(Role), null, "roles.json" }, + { typeof(Group), null, "groups.json" }, + { typeof(CustomField), null, "custom_fields.json" }, + { typeof(IssueStatus), null, "issue_statuses.json" }, + { typeof(Tracker), null, "trackers.json" }, + { typeof(IssuePriority), null, "enumerations/issue_priorities.json" }, + { typeof(TimeEntryActivity), null, "enumerations/time_entry_activities.json" } + }; + } + + public static TheoryData GetListEntityRequestOptionTestData() + { + var rqWithProjectId = new RequestOptions() + { + QueryString = new NameValueCollection() + { + {RedmineKeys.PROJECT_ID, "project1"} + } + }; + var rqWithPIssueId = new RequestOptions() + { + QueryString = new NameValueCollection() + { + {RedmineKeys.ISSUE_ID, "issue1"} + } + }; + return new TheoryData + { + { typeof(Version), rqWithProjectId, "projects/project1/versions.json" }, + { typeof(IssueCategory), rqWithProjectId, "projects/project1/issue_categories.json" }, + { typeof(ProjectMembership), rqWithProjectId, "projects/project1/memberships.json" }, + + { typeof(IssueRelation), rqWithPIssueId, "issues/issue1/relations.json" }, + + { typeof(File), rqWithProjectId, "projects/project1/files.json" }, + { typeof(Attachment), rqWithPIssueId, "attachments.json" }, + + { typeof(Issue), null, "issues.json" }, + { typeof(Project), null, "projects.json" }, + { typeof(User), null, "users.json" }, + { typeof(TimeEntry), null, "time_entries.json" }, + { typeof(News), null, "news.json" }, + { typeof(Query), null, "queries.json" }, + { typeof(Role), null, "roles.json" }, + { typeof(Group), null, "groups.json" }, + { typeof(CustomField), null, "custom_fields.json" }, + { typeof(IssueStatus), null, "issue_statuses.json" }, + { typeof(Tracker), null, "trackers.json" }, + { typeof(IssuePriority), null, "enumerations/issue_priorities.json" }, + { typeof(TimeEntryActivity), null, "enumerations/time_entry_activities.json" } + }; + } + + public static TheoryData GetListTestData() + { + return new TheoryData + { + { typeof(Version), "project1", "projects/project1/versions.json" }, + { typeof(IssueCategory), "project1", "projects/project1/issue_categories.json" }, + { typeof(ProjectMembership), "project1", "projects/project1/memberships.json" }, + + { typeof(IssueRelation), "issue1", "issues/issue1/relations.json" }, + + { typeof(File), "project1", "projects/project1/files.json" }, + + { typeof(Issue), null, "issues.json" }, + { typeof(Project), null, "projects.json" }, + { typeof(User), null, "users.json" }, + { typeof(TimeEntry), null, "time_entries.json" }, + { typeof(News), null, "news.json" }, + { typeof(Query), null, "queries.json" }, + { typeof(Role), null, "roles.json" }, + { typeof(Group), null, "groups.json" }, + { typeof(CustomField), null, "custom_fields.json" }, + { typeof(IssueStatus), null, "issue_statuses.json" }, + { typeof(Tracker), null, "trackers.json" }, + { typeof(IssuePriority), null, "enumerations/issue_priorities.json" }, + { typeof(TimeEntryActivity), null, "enumerations/time_entry_activities.json" } + }; + } + + public static TheoryData GetListWithIssueIdTestData() + { + return new TheoryData + { + { typeof(IssueRelation), "issue1", "issues/issue1/relations.json" }, + }; + } + + public static TheoryData GetListWithProjectIdTestData() + { + return new TheoryData + { + { typeof(Version), "1", "projects/1/versions.json" }, + { typeof(IssueCategory), "1", "projects/1/issue_categories.json" }, + { typeof(ProjectMembership), "1", "projects/1/memberships.json" }, + { typeof(File), "1", "projects/1/files.json" }, + }; + } + + public static TheoryData GetListWithNullRequestOptionsTestData() + { + return new TheoryData + { + { typeof(Issue), "issues.json" }, + { typeof(Project), "projects.json" }, + { typeof(User), "users.json" } + }; + } + + public static TheoryData GetListWithEmptyQueryStringTestData() + { + return new TheoryData + { + { typeof(Issue), "issues.json" }, + { typeof(Project), "projects.json" }, + { typeof(User), "users.json" } + }; + } + + public static TheoryData GetListWithInvalidTypeTestData() + { + return + [ + typeof(string), + typeof(int), + typeof(DateTime), + typeof(object) + ]; + } + + public static TheoryData GetListWithNoIdsTestData() + { + return new TheoryData + { + { typeof(Issue), "issues.json" }, + { typeof(Project), "projects.json" }, + { typeof(User), "users.json" }, + { typeof(TimeEntry), "time_entries.json" }, + { typeof(CustomField), "custom_fields.json" } + }; + } + + public static TheoryData InvalidTypeTestData() + { + return + [ + typeof(object), + typeof(int) + ]; + } + + public static TheoryData, string> AttachmentOperationsData() + { + var fixture = new RedmineApiUrlsFixture(); + return new TheoryData, string> + { + { + "123", + id => fixture.Sut.AttachmentUpdate(id), + "attachments/issues/123.json" + }, + { + "456", + id => fixture.Sut.IssueWatcherAdd(id), + "issues/456/watchers.json" + } + }; + } + + public static TheoryData, string> ProjectOperationsData() + { + var fixture = new RedmineApiUrlsFixture(); + return new TheoryData, string> + { + { + "test-project", + id => fixture.Sut.ProjectClose(id), + "projects/test-project/close.json" + }, + { + "test-project", + id => fixture.Sut.ProjectReopen(id), + "projects/test-project/reopen.json" + }, + { + "test-project", + id => fixture.Sut.ProjectArchive(id), + "projects/test-project/archive.json" + }, + { + "test-project", + id => fixture.Sut.ProjectUnarchive(id), + "projects/test-project/unarchive.json" + } + }; + } + + public static TheoryData, string> WikiOperationsData() + { + var fixture = new RedmineApiUrlsFixture(); + return new TheoryData, string> + { + { + "project1", + "page1", + (id, page) => fixture.Sut.ProjectWikiPage(id, page), + "projects/project1/wiki/page1.json" + }, + { + "project1", + "page1", + (id, page) => fixture.Sut.ProjectWikiPageCreate(id, page), + "projects/project1/wiki/page1.json" + } + }; + } + +} \ No newline at end of file From 2d353846737c43ccca7bd57a342420b336af9240 Mon Sep 17 00:00:00 2001 From: zapadi Date: Sun, 30 Mar 2025 22:47:20 +0300 Subject: [PATCH 13/14] [#371] Add tests --- .../Bugs/RedmineApi-371.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/redmine-net-api.Tests/Bugs/RedmineApi-371.cs b/tests/redmine-net-api.Tests/Bugs/RedmineApi-371.cs index c77ac1c1..08b3049d 100644 --- a/tests/redmine-net-api.Tests/Bugs/RedmineApi-371.cs +++ b/tests/redmine-net-api.Tests/Bugs/RedmineApi-371.cs @@ -1,5 +1,5 @@ using System.Collections.Specialized; -using Padi.DotNet.RedmineAPI.Tests.Infrastructure; +using Padi.DotNet.RedmineAPI.Tests.Tests; using Redmine.Net.Api.Extensions; using Redmine.Net.Api.Net; using Redmine.Net.Api.Types; @@ -7,24 +7,24 @@ namespace Padi.DotNet.RedmineAPI.Tests.Bugs; -public sealed class RedmineApi371 : IClassFixture +public sealed class RedmineApi371 : IClassFixture { - private readonly RedmineFixture _fixture; + private readonly RedmineApiUrlsFixture _fixture; - public RedmineApi371(RedmineFixture fixture) + public RedmineApi371(RedmineApiUrlsFixture fixture) { _fixture = fixture; } [Fact] - public void Should_Return_IssueCategories() + public void Should_Return_IssueCategories_For_Project_Url() { - var result = _fixture.RedmineManager.Get(new RequestOptions() - { - QueryString = new NameValueCollection() + var result = _fixture.Sut.GetListFragment( + new RequestOptions { - { "project_id", 1.ToInvariantString() } - } - }); + QueryString = new NameValueCollection{ { "project_id", 1.ToInvariantString() } } + }); + + Assert.Equal($"projects/1/issue_categories.{_fixture.Format}", result); } } \ No newline at end of file From fbd62ea86ef1baa6c2698a7c74433da8167f7402 Mon Sep 17 00:00:00 2001 From: zapadi Date: Wed, 12 Mar 2025 22:36:15 +0200 Subject: [PATCH 14/14] Equality tests --- .../Equality/AttachmentEqualityTests.cs | 66 ++++++++++ .../Equality/BaseEqualityTests.cs | 66 ++++++++++ .../Equality/CustomFieldPossibleValueTests.cs | 24 ++++ .../Equality/CustomFieldRoleTests.cs | 24 ++++ .../Equality/CustomFieldTests.cs | 34 ++++++ .../Equality/DetailTests.cs | 28 +++++ .../Equality/ErrorTests.cs | 16 +++ .../Equality/GroupTests.cs | 26 ++++ .../Equality/GroupUserTests.cs | 24 ++++ .../Equality/IssueCategoryTests.cs | 28 +++++ .../Equality/IssueEqualityTests.cs | 114 ++++++++++++++++++ .../Equality/IssueStatusTests.cs | 28 +++++ .../Equality/JournalEqualityTests.cs | 72 +++++++++++ .../Equality/MembershipTests.cs | 28 +++++ .../Equality/MyAccountCustomFieldTests.cs | 26 ++++ .../Equality/MyAccountTests.cs | 40 ++++++ .../Equality/NewsTests.cs | 37 ++++++ .../Equality/PermissionTests.cs | 22 ++++ .../Equality/ProjectMembershipTests.cs | 28 +++++ .../Equality/ProjectTests.cs | 91 ++++++++++++++ .../Equality/QueryTests.cs | 28 +++++ .../Equality/RoleTests.cs | 35 ++++++ .../Equality/SearchTests.cs | 33 +++++ .../Equality/TimeEntryActivityTests.cs | 28 +++++ .../Equality/TimeEntryTests.cs | 53 ++++++++ .../Equality/TrackerCoreFieldTests.cs | 16 +++ .../Equality/TrackerCustomFieldTests.cs | 24 ++++ .../Equality/UploadTests.cs | 28 +++++ .../Equality/UserGroupTests.cs | 25 ++++ .../Equality/UserTests.cs | 65 ++++++++++ .../Equality/VersionTests.cs | 47 ++++++++ .../Equality/WatcherTests.cs | 22 ++++ .../Equality/WikiPageTests.cs | 47 ++++++++ 33 files changed, 1273 insertions(+) create mode 100644 tests/redmine-net-api.Tests/Equality/AttachmentEqualityTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/BaseEqualityTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/CustomFieldPossibleValueTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/CustomFieldRoleTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/CustomFieldTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/DetailTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/ErrorTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/GroupTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/GroupUserTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/IssueCategoryTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/IssueEqualityTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/IssueStatusTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/JournalEqualityTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/MembershipTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/MyAccountCustomFieldTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/MyAccountTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/NewsTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/PermissionTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/ProjectMembershipTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/ProjectTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/QueryTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/RoleTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/SearchTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/TimeEntryActivityTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/TimeEntryTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/TrackerCoreFieldTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/TrackerCustomFieldTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/UploadTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/UserGroupTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/UserTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/VersionTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/WatcherTests.cs create mode 100644 tests/redmine-net-api.Tests/Equality/WikiPageTests.cs diff --git a/tests/redmine-net-api.Tests/Equality/AttachmentEqualityTests.cs b/tests/redmine-net-api.Tests/Equality/AttachmentEqualityTests.cs new file mode 100644 index 00000000..6cc72dd1 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/AttachmentEqualityTests.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class AttachmentEqualityTests +{ + [Fact] + public void Equals_SameReference_ReturnsTrue() + { + var attachment = CreateSampleAttachment(); + Assert.True(attachment.Equals(attachment)); + } + + [Fact] + public void Equals_Null_ReturnsFalse() + { + var attachment = CreateSampleAttachment(); + Assert.False(attachment.Equals(null)); + } + + [Theory] + [MemberData(nameof(GetDifferentAttachments))] + public void Equals_DifferentProperties_ReturnsFalse(Attachment attachment1, Attachment attachment2, string propertyName) + { + Assert.False(attachment1.Equals(attachment2), $"Attachments should not be equal when {propertyName} is different"); + } + + public static IEnumerable GetDifferentAttachments() + { + var baseAttachment = CreateSampleAttachment(); + + // Different FileName + var differentFileName = CreateSampleAttachment(); + differentFileName.FileName = "different.txt"; + yield return [baseAttachment, differentFileName, "FileName"]; + + // Different FileSize + var differentFileSize = CreateSampleAttachment(); + differentFileSize.FileSize = 2048; + yield return [baseAttachment, differentFileSize, "FileSize"]; + + // Different Author + var differentAuthor = CreateSampleAttachment(); + differentAuthor.Author = new IdentifiableName { Id = 999, Name = "Different Author" }; + yield return [baseAttachment, differentAuthor, "Author"]; + } + + private static Attachment CreateSampleAttachment() + { + return new Attachment + { + Id = 1, + FileName = "test.txt", + FileSize = 1024, + ContentType = "text/plain", + Description = "Test file", + ContentUrl = "https://example.com/test.txt", + ThumbnailUrl = "https://example.com/thumb.txt", + Author = new IdentifiableName { Id = 1, Name = "John Doe" }, + CreatedOn = DateTime.Now + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/BaseEqualityTests.cs b/tests/redmine-net-api.Tests/Equality/BaseEqualityTests.cs new file mode 100644 index 00000000..5ff00d44 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/BaseEqualityTests.cs @@ -0,0 +1,66 @@ +using System; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public abstract class BaseEqualityTests where T : class, IEquatable + { + protected abstract T CreateSampleInstance(); + protected abstract T CreateDifferentInstance(); + + [Fact] + public void Equals_SameReference_ReturnsTrue() + { + var instance = CreateSampleInstance(); + Assert.True(instance.Equals(instance)); + } + + [Fact] + public void Equals_Null_ReturnsFalse() + { + var instance = CreateSampleInstance(); + Assert.False(instance.Equals(null)); + } + + [Fact] + public void Equals_DifferentType_ReturnsFalse() + { + var instance = CreateSampleInstance(); + var differentObject = new object(); + Assert.False(instance.Equals(differentObject)); + } + + [Fact] + public void Equals_IdenticalProperties_ReturnsTrue() + { + var instance1 = CreateSampleInstance(); + var instance2 = CreateSampleInstance(); + Assert.True(instance1.Equals(instance2)); + Assert.True(instance2.Equals(instance1)); + } + + [Fact] + public void Equals_DifferentProperties_ReturnsFalse() + { + var instance1 = CreateSampleInstance(); + var instance2 = CreateDifferentInstance(); + Assert.False(instance1.Equals(instance2)); + Assert.False(instance2.Equals(instance1)); + } + + [Fact] + public void GetHashCode_SameProperties_ReturnsSameValue() + { + var instance1 = CreateSampleInstance(); + var instance2 = CreateSampleInstance(); + Assert.Equal(instance1.GetHashCode(), instance2.GetHashCode()); + } + + [Fact] + public void GetHashCode_DifferentProperties_ReturnsDifferentValues() + { + var instance1 = CreateSampleInstance(); + var instance2 = CreateDifferentInstance(); + Assert.NotEqual(instance1.GetHashCode(), instance2.GetHashCode()); + } + } \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/CustomFieldPossibleValueTests.cs b/tests/redmine-net-api.Tests/Equality/CustomFieldPossibleValueTests.cs new file mode 100644 index 00000000..c6d0fdfc --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/CustomFieldPossibleValueTests.cs @@ -0,0 +1,24 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class CustomFieldPossibleValueTests : BaseEqualityTests +{ + protected override CustomFieldPossibleValue CreateSampleInstance() + { + return new CustomFieldPossibleValue + { + Value = "test-value", + Label = "Test Label" + }; + } + + protected override CustomFieldPossibleValue CreateDifferentInstance() + { + return new CustomFieldPossibleValue + { + Value = "different-value", + Label = "Different Label" + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/CustomFieldRoleTests.cs b/tests/redmine-net-api.Tests/Equality/CustomFieldRoleTests.cs new file mode 100644 index 00000000..7026ff0c --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/CustomFieldRoleTests.cs @@ -0,0 +1,24 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class CustomFieldRoleTests : BaseEqualityTests +{ + protected override IdentifiableName CreateSampleInstance() + { + return new CustomFieldRole + { + Id = 1, + Name = "Test Role" + }; + } + + protected override IdentifiableName CreateDifferentInstance() + { + return new CustomFieldRole + { + Id = 2, + Name = "Different Role" + }; + } +} diff --git a/tests/redmine-net-api.Tests/Equality/CustomFieldTests.cs b/tests/redmine-net-api.Tests/Equality/CustomFieldTests.cs new file mode 100644 index 00000000..4ae461b5 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/CustomFieldTests.cs @@ -0,0 +1,34 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class CustomFieldTests : BaseEqualityTests +{ + protected override CustomField CreateSampleInstance() + { + return new CustomField + { + Id = 1, + Name = "Test Field", + CustomizedType = "issue", + FieldFormat = "string", + Regexp = "", + MinLength = 0, + MaxLength = 100, + IsRequired = false, + IsFilter = true, + Searchable = true, + Multiple = false, + DefaultValue = "default", + Visible = true, + PossibleValues = [new CustomFieldPossibleValue { Value = "value1", Label = "Label 1" }] + }; + } + + protected override CustomField CreateDifferentInstance() + { + var field = CreateSampleInstance(); + field.Name = "Different Field"; + return field; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/DetailTests.cs b/tests/redmine-net-api.Tests/Equality/DetailTests.cs new file mode 100644 index 00000000..296a21d3 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/DetailTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class DetailTests : BaseEqualityTests +{ + protected override Detail CreateSampleInstance() + { + return new Detail + { + Property = "status", + Name = "Status", + OldValue = "1", + NewValue = "2" + }; + } + + protected override Detail CreateDifferentInstance() + { + return new Detail + { + Property = "priority", + Name = "Priority", + OldValue = "3", + NewValue = "4" + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/ErrorTests.cs b/tests/redmine-net-api.Tests/Equality/ErrorTests.cs new file mode 100644 index 00000000..46f52c3b --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/ErrorTests.cs @@ -0,0 +1,16 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class ErrorTests : BaseEqualityTests +{ + protected override Error CreateSampleInstance() + { + return new Error( "Test error" ); + } + + protected override Error CreateDifferentInstance() + { + return new Error("Different error"); + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/GroupTests.cs b/tests/redmine-net-api.Tests/Equality/GroupTests.cs new file mode 100644 index 00000000..ee272db1 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/GroupTests.cs @@ -0,0 +1,26 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class GroupTests : BaseEqualityTests +{ + protected override Group CreateSampleInstance() + { + return new Group + { + Id = 1, + Name = "Test Group", + Users = [new GroupUser { Id = 1, Name = "User 1" }], + CustomFields = [new IssueCustomField { Id = 1, Name = "Field 1" }], + Memberships = [new Membership { Id = 1, Project = new IdentifiableName { Id = 1, Name = "Project 1" } }] + }; + } + + protected override Group CreateDifferentInstance() + { + var group = CreateSampleInstance(); + group.Name = "Different Group"; + group.Users = [new GroupUser { Id = 2, Name = "User 2" }]; + return group; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/GroupUserTests.cs b/tests/redmine-net-api.Tests/Equality/GroupUserTests.cs new file mode 100644 index 00000000..f177b4eb --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/GroupUserTests.cs @@ -0,0 +1,24 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class GroupUserTests : BaseEqualityTests +{ + protected override IdentifiableName CreateSampleInstance() + { + return new GroupUser + { + Id = 1, + Name = "Test User" + }; + } + + protected override IdentifiableName CreateDifferentInstance() + { + return new GroupUser + { + Id = 2, + Name = "Different User" + }; + } +} diff --git a/tests/redmine-net-api.Tests/Equality/IssueCategoryTests.cs b/tests/redmine-net-api.Tests/Equality/IssueCategoryTests.cs new file mode 100644 index 00000000..1a00e021 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/IssueCategoryTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class IssueCategoryTests : BaseEqualityTests +{ + protected override IssueCategory CreateSampleInstance() + { + return new IssueCategory + { + Id = 1, + Name = "Test Category", + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + AssignTo = new IdentifiableName { Id = 1, Name = "User 1" } + }; + } + + protected override IssueCategory CreateDifferentInstance() + { + return new IssueCategory + { + Id = 2, + Name = "Different Category", + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + AssignTo = new IdentifiableName { Id = 2, Name = "User 2" } + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/IssueEqualityTests.cs b/tests/redmine-net-api.Tests/Equality/IssueEqualityTests.cs new file mode 100644 index 00000000..90dcc08a --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/IssueEqualityTests.cs @@ -0,0 +1,114 @@ +using System; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class IssueEqualityTests +{ + [Fact] + public void Equals_SameReference_ReturnsTrue() + { + var issue = CreateSampleIssue(); + Assert.True(issue.Equals(issue)); + } + + [Fact] + public void Equals_Null_ReturnsFalse() + { + var issue = CreateSampleIssue(); + Assert.False(issue.Equals(null)); + } + + [Fact] + public void Equals_DifferentType_ReturnsFalse() + { + var issue = CreateSampleIssue(); + var differentObject = new object(); + Assert.False(issue.Equals(differentObject)); + } + + [Fact] + public void Equals_IdenticalProperties_ReturnsTrue() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + Assert.True(issue1.Equals(issue2)); + Assert.True(issue2.Equals(issue1)); + } + + [Fact] + public void GetHashCode_SameProperties_ReturnsSameValue() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + Assert.Equal(issue1.GetHashCode(), issue2.GetHashCode()); + } + + [Fact] + public void OperatorEquals_SameObjects_ReturnsTrue() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + Assert.True(issue1 == issue2); + } + + [Fact] + public void OperatorNotEquals_DifferentObjects_ReturnsTrue() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + issue2.Subject = "Different Subject"; + Assert.True(issue1 != issue2); + } + + [Fact] + public void Equals_NullCollections_ReturnsTrue() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + issue1.CustomFields = null; + issue2.CustomFields = null; + Assert.True(issue1.Equals(issue2)); + } + + [Fact] + public void Equals_DifferentCollectionSizes_ReturnsFalse() + { + var issue1 = CreateSampleIssue(); + var issue2 = CreateSampleIssue(); + issue2.CustomFields.Add(new IssueCustomField { Id = 2, Name = "Additional Field" }); + Assert.False(issue1.Equals(issue2)); + } + + private static Issue CreateSampleIssue() + { + return new Issue + { + Id = 1, + Project = new IdentifiableName { Id = 100, Name = "Test Project" }, + Tracker = new IdentifiableName { Id = 1, Name = "Bug" }, + Status = new IdentifiableName { Id = 1, Name = "New" }, + Priority = new IdentifiableName { Id = 1, Name = "Normal" }, + Author = new IdentifiableName { Id = 1, Name = "John Doe" }, + Subject = "Test Issue", + Description = "Test Description", + StartDate = new DateTime(2025, 02,02,10,10,10).Date, + DueDate = new DateTime(2025, 02,02,10,10,10).Date.AddDays(7), + DoneRatio = 0, + IsPrivate = false, + EstimatedHours = 8.5f, + CreatedOn = new DateTime(2025, 02,02,10,10,10), + UpdatedOn = new DateTime(2025, 02,04,15,10,5), + CustomFields = + [ + new IssueCustomField + { + Id = 1, + Name = "Custom Field 1", + Values = [new CustomFieldValue("Value 1")] + } + ] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/IssueStatusTests.cs b/tests/redmine-net-api.Tests/Equality/IssueStatusTests.cs new file mode 100644 index 00000000..793f7e5c --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/IssueStatusTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class IssueStatusTests : BaseEqualityTests +{ + protected override IssueStatus CreateSampleInstance() + { + return new IssueStatus + { + Id = 1, + Name = "New", + IsDefault = true, + IsClosed = false + }; + } + + protected override IssueStatus CreateDifferentInstance() + { + return new IssueStatus + { + Id = 2, + Name = "Closed", + IsDefault = false, + IsClosed = true + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/JournalEqualityTests.cs b/tests/redmine-net-api.Tests/Equality/JournalEqualityTests.cs new file mode 100644 index 00000000..16f2a073 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/JournalEqualityTests.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using Redmine.Net.Api.Types; +using Xunit; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class JournalEqualityTests +{ + [Fact] + public void Equals_SameReference_ReturnsTrue() + { + var journal = CreateSampleJournal(); + Assert.True(journal.Equals(journal)); + } + + [Fact] + public void Equals_Null_ReturnsFalse() + { + var journal = CreateSampleJournal(); + Assert.False(journal.Equals(null)); + } + + [Theory] + [MemberData(nameof(GetDifferentJournals))] + public void Equals_DifferentProperties_ReturnsFalse(Journal journal1, Journal journal2, string propertyName) + { + Assert.False(journal1.Equals(journal2), $"Journals should not be equal when {propertyName} is different"); + } + + public static IEnumerable GetDifferentJournals() + { + var baseJournal = CreateSampleJournal(); + + // Different Notes + var differentNotes = CreateSampleJournal(); + differentNotes.Notes = "Different notes"; + yield return [baseJournal, differentNotes, "Notes"]; + + // Different User + var differentUser = CreateSampleJournal(); + differentUser.User = new IdentifiableName { Id = 999, Name = "Different User" }; + yield return [baseJournal, differentUser, "User"]; + + // Different Details + var differentDetails = CreateSampleJournal(); + differentDetails.Details[0].NewValue = "Different value"; + yield return [baseJournal, differentDetails, "Details"]; + } + + private static Journal CreateSampleJournal() + { + return new Journal + { + Id = 1, + User = new IdentifiableName { Id = 1, Name = "John Doe" }, + Notes = "Test notes", + CreatedOn = new DateTime(2025,02,14,14,04,00), + PrivateNotes = true, + Details = + [ + new Detail + { + Property = "status_id", + Name = "Status", + OldValue = "1", + NewValue = "2" + } + ] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/MembershipTests.cs b/tests/redmine-net-api.Tests/Equality/MembershipTests.cs new file mode 100644 index 00000000..41f15546 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/MembershipTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class MembershipTests : BaseEqualityTests +{ + protected override Membership CreateSampleInstance() + { + return new Membership + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + User = new IdentifiableName { Id = 1, Name = "User 1" }, + Roles = [new MembershipRole { Id = 1, Name = "Developer", Inherited = false }] + }; + } + + protected override Membership CreateDifferentInstance() + { + return new Membership + { + Id = 2, + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + User = new IdentifiableName { Id = 2, Name = "User 2" }, + Roles = [new MembershipRole { Id = 2, Name = "Manager", Inherited = true }] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/MyAccountCustomFieldTests.cs b/tests/redmine-net-api.Tests/Equality/MyAccountCustomFieldTests.cs new file mode 100644 index 00000000..aa2036d6 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/MyAccountCustomFieldTests.cs @@ -0,0 +1,26 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public class MyAccountCustomFieldTests : BaseEqualityTests +{ + protected override MyAccountCustomField CreateSampleInstance() + { + return new MyAccountCustomField + { + Id = 1, + Name = "Test Field", + Value = "Test Value", + }; + } + + protected override MyAccountCustomField CreateDifferentInstance() + { + return new MyAccountCustomField + { + Id = 2, + Name = "Different Field", + Value = "Different Value", + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/MyAccountTests.cs b/tests/redmine-net-api.Tests/Equality/MyAccountTests.cs new file mode 100644 index 00000000..6c0fad96 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/MyAccountTests.cs @@ -0,0 +1,40 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class MyAccountTests : BaseEqualityTests +{ + protected override MyAccount CreateSampleInstance() + { + return new MyAccount + { + Id = 1, + Login = "testaccount", + FirstName = "Test", + LastName = "Account", + Email = "test@example.com", + CreatedOn = new DateTime(2023, 1, 1).Date, + LastLoginOn = new DateTime(2023, 1, 1).Date, + ApiKey = "abc123", + CustomFields = [ + new MyAccountCustomField() { Value = "Value 1" } + ] + }; + } + + protected override MyAccount CreateDifferentInstance() + { + return new MyAccount + { + Id = 2, + Login = "differentaccount", + FirstName = "Different", + LastName = "Account", + Email = "different@example.com", + CreatedOn = new DateTime(2023, 1, 2).Date, + LastLoginOn = new DateTime(2023, 1, 2).Date, + ApiKey = "xyz789" + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/NewsTests.cs b/tests/redmine-net-api.Tests/Equality/NewsTests.cs new file mode 100644 index 00000000..953775ea --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/NewsTests.cs @@ -0,0 +1,37 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class NewsTests : BaseEqualityTests +{ + protected override News CreateSampleInstance() + { + return new News + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + Author = new IdentifiableName { Id = 1, Name = "Author 1" }, + Title = "Test News", + Summary = "Test Summary", + Description = "Test Description", + CreatedOn = new DateTime(2023, 1, 1, 0, 0, 0).Date, + Comments = [new NewsComment { Id = 1, Content = "Test Comment" }] + }; + } + + protected override News CreateDifferentInstance() + { + return new News + { + Id = 2, + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + Author = new IdentifiableName { Id = 2, Name = "Author 2" }, + Title = "Different News", + Summary = "Different Summary", + Description = "Different Description", + CreatedOn = new DateTime(2023, 1, 2).Date, + Comments = [new NewsComment { Id = 2, Content = "Different Comment" }] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/PermissionTests.cs b/tests/redmine-net-api.Tests/Equality/PermissionTests.cs new file mode 100644 index 00000000..cc35641b --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/PermissionTests.cs @@ -0,0 +1,22 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class PermissionTests : BaseEqualityTests +{ + protected override Permission CreateSampleInstance() + { + return new Permission + { + Info = "add_issues" + }; + } + + protected override Permission CreateDifferentInstance() + { + return new Permission + { + Info = "edit_issues" + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/ProjectMembershipTests.cs b/tests/redmine-net-api.Tests/Equality/ProjectMembershipTests.cs new file mode 100644 index 00000000..6ed4ea0d --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/ProjectMembershipTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class ProjectMembershipTests : BaseEqualityTests +{ + protected override ProjectMembership CreateSampleInstance() + { + return new ProjectMembership + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + User = new IdentifiableName { Id = 1, Name = "User 1" }, + Roles = [new MembershipRole { Id = 1, Name = "Developer" }] + }; + } + + protected override ProjectMembership CreateDifferentInstance() + { + return new ProjectMembership + { + Id = 2, + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + User = new IdentifiableName { Id = 2, Name = "User 2" }, + Roles = [new MembershipRole { Id = 2, Name = "Manager" }] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/ProjectTests.cs b/tests/redmine-net-api.Tests/Equality/ProjectTests.cs new file mode 100644 index 00000000..2f2ce5a6 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/ProjectTests.cs @@ -0,0 +1,91 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class ProjectTests : BaseEqualityTests +{ + protected override Project CreateSampleInstance() + { + return new Project + { + Id = 1, + Name = "Test Project", + Identifier = "test-project", + Description = "Test Description", + HomePage = "https://test.com", + Status = ProjectStatus.Active, + IsPublic = true, + InheritMembers = true, + DefaultAssignee = new IdentifiableName(5, "DefaultAssignee"), + DefaultVersion = new IdentifiableName(5, "DefaultVersion"), + Parent = new IdentifiableName { Id = 1, Name = "Parent Project" }, + CreatedOn = new DateTime(2023, 1, 1).Date, + UpdatedOn = new DateTime(2023, 1, 1).Date, + Trackers = + [ + new() { Id = 1, Name = "Bug" }, + new() { Id = 2, Name = "Feature" } + ], + + CustomFields = + [ + new() { Id = 1, Name = "Field1"}, + new() { Id = 2, Name = "Field2"} + ], + + IssueCategories = + [ + new() { Id = 1, Name = "Category1" }, + new() { Id = 2, Name = "Category2" } + ], + EnabledModules = + [ + new() { Id = 1, Name = "Module1" }, + new() { Id = 2, Name = "Module2" } + ], + TimeEntryActivities = + [ + new() { Id = 1, Name = "Activity1" }, + new() { Id = 2, Name = "Activity2" } + ] + }; + } + + protected override Project CreateDifferentInstance() + { + return new Project + { + Id = 2, + Name = "Different Project", + Identifier = "different-project", + Description = "Different Description", + HomePage = "https://different.com", + Status = ProjectStatus.Archived, + IsPublic = false, + Parent = new IdentifiableName { Id = 2, Name = "Different Parent" }, + CreatedOn = new DateTime(2023, 1, 2).Date, + UpdatedOn = new DateTime(2023, 1, 2).Date, + Trackers = + [ + new() { Id = 3, Name = "Different Bug" } + ], + CustomFields = + [ + new() { Id = 3, Name = "DifferentField"} + ], + IssueCategories = + [ + new() { Id = 3, Name = "DifferentCategory" } + ], + EnabledModules = + [ + new() { Id = 3, Name = "DifferentModule" } + ], + TimeEntryActivities = + [ + new() { Id = 3, Name = "DifferentActivity" } + ] + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/QueryTests.cs b/tests/redmine-net-api.Tests/Equality/QueryTests.cs new file mode 100644 index 00000000..74b8beee --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/QueryTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class QueryTests : BaseEqualityTests +{ + protected override Query CreateSampleInstance() + { + return new Query + { + Id = 1, + Name = "Test Query", + IsPublic = true, + ProjectId = 1 + }; + } + + protected override Query CreateDifferentInstance() + { + return new Query + { + Id = 2, + Name = "Different Query", + IsPublic = false, + ProjectId = 2 + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/RoleTests.cs b/tests/redmine-net-api.Tests/Equality/RoleTests.cs new file mode 100644 index 00000000..8879c4ba --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/RoleTests.cs @@ -0,0 +1,35 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class RoleTests : BaseEqualityTests +{ + protected override Role CreateSampleInstance() + { + return new Role + { + Id = 1, + Name = "Developer", + Permissions = + [ + new Permission { Info = "add_issues" }, + new Permission { Info = "edit_issues" } + ], + IsAssignable = true + }; + } + + protected override Role CreateDifferentInstance() + { + return new Role + { + Id = 2, + Name = "Manager", + Permissions = + [ + new Permission { Info = "manage_project" } + ], + IsAssignable = false + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/SearchTests.cs b/tests/redmine-net-api.Tests/Equality/SearchTests.cs new file mode 100644 index 00000000..4962b131 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/SearchTests.cs @@ -0,0 +1,33 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class SearchTests : BaseEqualityTests +{ + protected override Search CreateSampleInstance() + { + return new Search + { + Id = 1, + Title = "Test Search", + Type = "issue", + Url = "http://example.com/search", + Description = "Test Description", + DateTime = new DateTime(2023, 1, 1).Date + }; + } + + protected override Search CreateDifferentInstance() + { + return new Search + { + Id = 2, + Title = "Different Search", + Type = "wiki", + Url = "http://example.com/different", + Description = "Different Description", + DateTime = new DateTime(2023, 1, 2).Date + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/TimeEntryActivityTests.cs b/tests/redmine-net-api.Tests/Equality/TimeEntryActivityTests.cs new file mode 100644 index 00000000..a1554384 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/TimeEntryActivityTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class TimeEntryActivityTests : BaseEqualityTests +{ + protected override TimeEntryActivity CreateSampleInstance() + { + return new TimeEntryActivity + { + Id = 1, + Name = "Development", + IsDefault = true, + IsActive = true + }; + } + + protected override TimeEntryActivity CreateDifferentInstance() + { + return new TimeEntryActivity + { + Id = 2, + Name = "Testing", + IsDefault = false, + IsActive = false + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/TimeEntryTests.cs b/tests/redmine-net-api.Tests/Equality/TimeEntryTests.cs new file mode 100644 index 00000000..6e1c3426 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/TimeEntryTests.cs @@ -0,0 +1,53 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class TimeEntryTests : BaseEqualityTests +{ + protected override TimeEntry CreateSampleInstance() + { + return new TimeEntry + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + Issue = new IdentifiableName { Id = 1, Name = "Issue 1" }, + User = new IdentifiableName { Id = 1, Name = "User 1" }, + Activity = new IdentifiableName { Id = 1, Name = "Development" }, + Hours = (decimal)8.0, + Comments = "Work done", + SpentOn = new DateTime(2023, 1, 1).Date, + CreatedOn = new DateTime(2023, 1, 1).Date, + UpdatedOn = new DateTime(2023, 1, 1).Date, + CustomFields = + [ + new IssueCustomField + { + Id = 1, + Name = "Field 1", + Values = + [ + new CustomFieldValue("value") + ] + } + ] + }; + } + + protected override TimeEntry CreateDifferentInstance() + { + return new TimeEntry + { + Id = 2, + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + Issue = new IdentifiableName { Id = 2, Name = "Issue 2" }, + User = new IdentifiableName { Id = 2, Name = "User 2" }, + Activity = new IdentifiableName { Id = 2, Name = "Testing" }, + Hours = (decimal)4.0, + Comments = "Different work", + SpentOn = new DateTime(2023, 1, 2).Date, + CreatedOn = new DateTime(2023, 1, 2).Date, + UpdatedOn = new DateTime(2023, 1, 2).Date + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/TrackerCoreFieldTests.cs b/tests/redmine-net-api.Tests/Equality/TrackerCoreFieldTests.cs new file mode 100644 index 00000000..24f6cecb --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/TrackerCoreFieldTests.cs @@ -0,0 +1,16 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class TrackerCoreFieldTests : BaseEqualityTests +{ + protected override TrackerCoreField CreateSampleInstance() + { + return new TrackerCoreField("Developer"); + } + + protected override TrackerCoreField CreateDifferentInstance() + { + return new TrackerCoreField("Admin"); + } +} diff --git a/tests/redmine-net-api.Tests/Equality/TrackerCustomFieldTests.cs b/tests/redmine-net-api.Tests/Equality/TrackerCustomFieldTests.cs new file mode 100644 index 00000000..e06fd6ea --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/TrackerCustomFieldTests.cs @@ -0,0 +1,24 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class TrackerCustomFieldTests : BaseEqualityTests +{ + protected override IdentifiableName CreateSampleInstance() + { + return new TrackerCustomField + { + Id = 1, + Name = "Test Field" + }; + } + + protected override IdentifiableName CreateDifferentInstance() + { + return new TrackerCustomField + { + Id = 2, + Name = "Different Field" + }; + } +} diff --git a/tests/redmine-net-api.Tests/Equality/UploadTests.cs b/tests/redmine-net-api.Tests/Equality/UploadTests.cs new file mode 100644 index 00000000..48ded660 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/UploadTests.cs @@ -0,0 +1,28 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class UploadTests : BaseEqualityTests +{ + protected override Upload CreateSampleInstance() + { + return new Upload + { + Token = "abc123", + FileName = "test.pdf", + ContentType = "application/pdf", + Description = "Test Upload" + }; + } + + protected override Upload CreateDifferentInstance() + { + return new Upload + { + Token = "xyz789", + FileName = "different.pdf", + ContentType = "application/pdf", + Description = "Different Upload" + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/UserGroupTests.cs b/tests/redmine-net-api.Tests/Equality/UserGroupTests.cs new file mode 100644 index 00000000..005809cb --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/UserGroupTests.cs @@ -0,0 +1,25 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class UserGroupTests : BaseEqualityTests +{ + protected override IdentifiableName CreateSampleInstance() + { + return new UserGroup + { + Id = 1, + Name = "Test Group" + }; + } + + protected override IdentifiableName CreateDifferentInstance() + { + return new UserGroup + { + Id = 2, + Name = "Different Group" + }; + } +} + diff --git a/tests/redmine-net-api.Tests/Equality/UserTests.cs b/tests/redmine-net-api.Tests/Equality/UserTests.cs new file mode 100644 index 00000000..ffc52415 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/UserTests.cs @@ -0,0 +1,65 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class UserTests : BaseEqualityTests +{ + protected override User CreateSampleInstance() + { + return new User + { + Id = 1, + Login = "testuser", + FirstName = "Test", + LastName = "User", + Email = "test@example.com", + CreatedOn = new DateTime(2023, 1, 1).Date, + LastLoginOn = new DateTime(2023, 1, 1).Date, + ApiKey = "abc123", + Status = UserStatus.StatusActive, + IsAdmin = false, + CustomFields = + [ + new IssueCustomField + { + Id = 1, + Name = "Field 1", + Values = + [ + new CustomFieldValue("Value 1") + ] + } + ], + Memberships = + [ + new Membership + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" } + } + ], + Groups = + [ + new UserGroup { Id = 1, Name = "Group 1" } + ] + }; + } + + protected override User CreateDifferentInstance() + { + return new User + { + Id = 2, + Login = "differentuser", + FirstName = "Different", + LastName = "User", + Email = "different@example.com", + CreatedOn = new DateTime(2023, 1, 2).Date, + LastLoginOn = new DateTime(2023, 1, 2).Date, + ApiKey = "xyz789", + Status = UserStatus.StatusLocked, + IsAdmin = true + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/VersionTests.cs b/tests/redmine-net-api.Tests/Equality/VersionTests.cs new file mode 100644 index 00000000..5d3e06cc --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/VersionTests.cs @@ -0,0 +1,47 @@ +using System; +using Redmine.Net.Api.Types; +using Version = Redmine.Net.Api.Types.Version; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class VersionTests : BaseEqualityTests +{ + protected override Version CreateSampleInstance() + { + return new Version + { + Id = 1, + Project = new IdentifiableName { Id = 1, Name = "Project 1" }, + Name = "1.0.0", + Description = "First Release", + Status = VersionStatus.Open, + DueDate = new DateTime(2023, 12, 31).Date, + CreatedOn = new DateTime(2023, 1, 1).Date, + UpdatedOn = new DateTime(2023, 1, 1).Date, + Sharing = VersionSharing.None, + CustomFields = + [ + new IssueCustomField + { + Id = 1, Name = "Field 1", Values = [new CustomFieldValue("Value 1")] + } + ] + }; + } + + protected override Version CreateDifferentInstance() + { + return new Version + { + Id = 2, + Project = new IdentifiableName { Id = 2, Name = "Project 2" }, + Name = "2.0.0", + Description = "Second Release", + Status = VersionStatus.Closed, + DueDate = new DateTime(2024, 12, 31).Date, + CreatedOn = new DateTime(2023, 1, 2).Date, + UpdatedOn = new DateTime(2023, 1, 2).Date, + Sharing = VersionSharing.System + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/WatcherTests.cs b/tests/redmine-net-api.Tests/Equality/WatcherTests.cs new file mode 100644 index 00000000..300ece6f --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/WatcherTests.cs @@ -0,0 +1,22 @@ +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class WatcherTests : BaseEqualityTests +{ + protected override Watcher CreateSampleInstance() + { + return new Watcher + { + Id = 1, + }; + } + + protected override Watcher CreateDifferentInstance() + { + return new Watcher + { + Id = 2, + }; + } +} \ No newline at end of file diff --git a/tests/redmine-net-api.Tests/Equality/WikiPageTests.cs b/tests/redmine-net-api.Tests/Equality/WikiPageTests.cs new file mode 100644 index 00000000..6462f7e6 --- /dev/null +++ b/tests/redmine-net-api.Tests/Equality/WikiPageTests.cs @@ -0,0 +1,47 @@ +using System; +using Redmine.Net.Api.Types; + +namespace Padi.DotNet.RedmineAPI.Tests.Equality; + +public sealed class WikiPageTests : BaseEqualityTests +{ + protected override WikiPage CreateSampleInstance() + { + return new WikiPage + { + Id = 1, + Title = "Home Page", + Text = "Welcome to the wiki", + Version = 1, + Author = new IdentifiableName { Id = 1, Name = "Author 1" }, + Comments = "Initial version", + CreatedOn = new DateTime(2023, 1, 1), + UpdatedOn = new DateTime(2023, 1, 1), + Attachments = + [ + new Attachment + { + Id = 1, + FileName = "doc.pdf", + FileSize = 1024, + Author = new IdentifiableName { Id = 1, Name = "Author 1" } + } + ] + }; + } + + protected override WikiPage CreateDifferentInstance() + { + return new WikiPage + { + Id = 2, + Title = "Different Page", + Text = "Different content", + Version = 2, + Author = new IdentifiableName { Id = 2, Name = "Author 2" }, + Comments = "Updated version", + CreatedOn = new DateTime(2023, 1, 2), + UpdatedOn = new DateTime(2023, 1, 2) + }; + } +} \ No newline at end of file