From 210ae1dd754db0deb29df7ff78ae8bdd5f39a082 Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 04:02:23 +0300 Subject: [PATCH 1/7] Added duration property to the Item model. --- src/Todoist.Net/Models/Duration.cs | 70 ++++++++++++++++++++++++++ src/Todoist.Net/Models/DurationUnit.cs | 31 ++++++++++++ src/Todoist.Net/Models/Item.cs | 12 +++++ 3 files changed, 113 insertions(+) create mode 100644 src/Todoist.Net/Models/Duration.cs create mode 100644 src/Todoist.Net/Models/DurationUnit.cs diff --git a/src/Todoist.Net/Models/Duration.cs b/src/Todoist.Net/Models/Duration.cs new file mode 100644 index 0000000..6467257 --- /dev/null +++ b/src/Todoist.Net/Models/Duration.cs @@ -0,0 +1,70 @@ +using System; + +using Newtonsoft.Json; + +namespace Todoist.Net.Models +{ + /// + /// Represents durations for tasks. + /// + public class Duration + { + + /// + /// Initializes a new instance of the class. + /// + /// The time amount. + /// The time unit. + public Duration(int amount, DurationUnit unit) + { + Amount = amount > 0 + ? amount + : throw new ArgumentOutOfRangeException(nameof(amount), "Parameter must be greater than zero."); + Unit = unit + ?? throw new ArgumentNullException(nameof(unit)); + } + + internal Duration() + { + } + + /// + /// Gets or sets the duration time amount. + /// + /// + /// Must be a positive integer (greater than zero). + /// + /// + /// The time amount. + /// + [JsonProperty("amount")] + public int Amount { get; set; } + + /// + /// Gets or sets the duration time unit. + /// + /// + /// Either minute or day. + /// + /// + /// The duration unit. + /// + [JsonProperty("unit")] + public DurationUnit Unit { get; set; } + + + /// + /// Gets the value of the duration as a object. + /// + /// + /// The value of the duration. + /// + [JsonIgnore] + public TimeSpan TimeValue => Unit == DurationUnit.Minute + ? TimeSpan.FromMinutes(Amount) + : Unit == DurationUnit.Day + ? TimeSpan.FromDays(Amount) + : throw new NotImplementedException(); + + } +} diff --git a/src/Todoist.Net/Models/DurationUnit.cs b/src/Todoist.Net/Models/DurationUnit.cs new file mode 100644 index 0000000..234e9aa --- /dev/null +++ b/src/Todoist.Net/Models/DurationUnit.cs @@ -0,0 +1,31 @@ +namespace Todoist.Net.Models +{ + /// + /// Represents a duration unit. + /// + /// + public class DurationUnit : StringEnum + { + /// + /// Initializes a new instance of the class. + /// + /// The value. + private DurationUnit(string value) + : base(value) + { + } + + /// + /// Gets the minute unit. + /// + /// The minute unit. + public static DurationUnit Minute { get; } = new DurationUnit("minute"); + + /// + /// Gets the day unit. + /// + /// The day unit. + public static DurationUnit Day { get; } = new DurationUnit("day"); + + } +} diff --git a/src/Todoist.Net/Models/Item.cs b/src/Todoist.Net/Models/Item.cs index 57bb914..636e0b2 100644 --- a/src/Todoist.Net/Models/Item.cs +++ b/src/Todoist.Net/Models/Item.cs @@ -103,6 +103,18 @@ internal Item() [JsonProperty("due")] public DueDate DueDate { get; set; } + /// + /// Gets or sets the duration. + /// + /// + /// Durations are only available for Todoist Premium users. + /// + /// + /// The duration. + /// + [JsonProperty("duration")] + public Duration Duration { get; set; } + /// /// Gets a value indicating whether this instance is checked. /// From 881447b6039c5878f5c902ce89bbb682859a4f4a Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 04:31:25 +0300 Subject: [PATCH 2/7] Extracted the nested ternary operation in Duration model into an independent if statement. To follow the clean code rule: csharpsquid:S3358 (Ternary operators should not be nested) --- src/Todoist.Net/Models/Duration.cs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Todoist.Net/Models/Duration.cs b/src/Todoist.Net/Models/Duration.cs index 6467257..dccda38 100644 --- a/src/Todoist.Net/Models/Duration.cs +++ b/src/Todoist.Net/Models/Duration.cs @@ -60,11 +60,21 @@ internal Duration() /// The value of the duration. /// [JsonIgnore] - public TimeSpan TimeValue => Unit == DurationUnit.Minute - ? TimeSpan.FromMinutes(Amount) - : Unit == DurationUnit.Day - ? TimeSpan.FromDays(Amount) - : throw new NotImplementedException(); + public TimeSpan TimeValue + { + get + { + if (Unit == DurationUnit.Minute) + { + return TimeSpan.FromMinutes(Amount); + } + if (Unit == DurationUnit.Day) + { + return TimeSpan.FromDays(Amount); + } + throw new NotImplementedException(); + } + } } } From 0f4fbe0e06b65fc5407cee6c0be76d3da3b23aea Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 13:37:41 +0300 Subject: [PATCH 3/7] Replace if statements with switch pattern in Duration.TimeValue property getter. --- src/Todoist.Net/Models/Duration.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Todoist.Net/Models/Duration.cs b/src/Todoist.Net/Models/Duration.cs index dccda38..42e5589 100644 --- a/src/Todoist.Net/Models/Duration.cs +++ b/src/Todoist.Net/Models/Duration.cs @@ -64,15 +64,15 @@ public TimeSpan TimeValue { get { - if (Unit == DurationUnit.Minute) + switch (Unit) { - return TimeSpan.FromMinutes(Amount); + case var _ when Unit == DurationUnit.Minute: + return TimeSpan.FromMinutes(Amount); + case var _ when Unit == DurationUnit.Day: + return TimeSpan.FromDays(Amount); + default: + throw new NotImplementedException(); } - if (Unit == DurationUnit.Day) - { - return TimeSpan.FromDays(Amount); - } - throw new NotImplementedException(); } } From 166a0ac70a3387ee2d09f3b94a06ea2acff059bf Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 13:44:53 +0300 Subject: [PATCH 4/7] Moved Duration arguments validation to full properties. --- src/Todoist.Net/Models/Duration.cs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Todoist.Net/Models/Duration.cs b/src/Todoist.Net/Models/Duration.cs index 42e5589..e96c72f 100644 --- a/src/Todoist.Net/Models/Duration.cs +++ b/src/Todoist.Net/Models/Duration.cs @@ -10,6 +10,9 @@ namespace Todoist.Net.Models public class Duration { + private int _amount; + private DurationUnit _unit; + /// /// Initializes a new instance of the class. /// @@ -17,11 +20,8 @@ public class Duration /// The time unit. public Duration(int amount, DurationUnit unit) { - Amount = amount > 0 - ? amount - : throw new ArgumentOutOfRangeException(nameof(amount), "Parameter must be greater than zero."); - Unit = unit - ?? throw new ArgumentNullException(nameof(unit)); + Amount = amount; + Unit = unit; } internal Duration() @@ -37,8 +37,15 @@ internal Duration() /// /// The time amount. /// + /// Duration amount must be greater than zero." [JsonProperty("amount")] - public int Amount { get; set; } + public int Amount + { + get => _amount; + set => _amount = value > 0 + ? value + : throw new ArgumentOutOfRangeException(nameof(Amount), "Duration amount must be greater than zero."); + } /// /// Gets or sets the duration time unit. @@ -49,8 +56,13 @@ internal Duration() /// /// The duration unit. /// + /// Unit [JsonProperty("unit")] - public DurationUnit Unit { get; set; } + public DurationUnit Unit + { + get => _unit; + set => _unit = value ?? throw new ArgumentNullException(nameof(Unit)); + } /// From fc4f2b0772b6e16bcfbf19796df014af744a33e2 Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 14:06:17 +0300 Subject: [PATCH 5/7] Added unit tests for Duration model. --- src/Todoist.Net.Tests/Models/DurationTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/Todoist.Net.Tests/Models/DurationTests.cs diff --git a/src/Todoist.Net.Tests/Models/DurationTests.cs b/src/Todoist.Net.Tests/Models/DurationTests.cs new file mode 100644 index 0000000..976aede --- /dev/null +++ b/src/Todoist.Net.Tests/Models/DurationTests.cs @@ -0,0 +1,54 @@ +using System; + +using Todoist.Net.Models; +using Todoist.Net.Tests.Extensions; +using Xunit; + +namespace Todoist.Net.Tests.Models +{ + [Trait(Constants.TraitName, Constants.UnitTraitValue)] + public class DurationTests + { + [Fact] + public void AmountAssignment_InvalidValue_ThrowsException() + { + Duration duration; + + Assert.Throws(() => + duration = new Duration(0, DurationUnit.Minute)); + + duration = new Duration(15, DurationUnit.Minute); + + Assert.Throws(() => + duration.Amount = -5); + } + + [Fact] + public void UnitAssignment_InvalidValue_ThrowsException() + { + Duration duration; + + Assert.Throws(() => + duration = new Duration(15, null)); + + duration = new Duration(15, DurationUnit.Minute); + + Assert.Throws(() => + duration.Unit = null); + } + + [Fact] + public void TimeValueEvaluation_Success() + { + var duration = new Duration(15, DurationUnit.Minute); + + Assert.Equal(TimeSpan.FromMinutes(15), duration.TimeValue); + + duration.Amount = 3; + duration.Unit = DurationUnit.Day; + + Assert.Equal(TimeSpan.FromDays(3), duration.TimeValue); + } + + } +} From cf751826d36cf3bb60f48958b3dba7f7487ad7fb Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 17:24:23 +0300 Subject: [PATCH 6/7] Configured the JsonProperty attribute on Duration to include nulls. This fixes the issue with update requests that removes durations. --- src/Todoist.Net/Models/Item.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Todoist.Net/Models/Item.cs b/src/Todoist.Net/Models/Item.cs index 636e0b2..c3a7b84 100644 --- a/src/Todoist.Net/Models/Item.cs +++ b/src/Todoist.Net/Models/Item.cs @@ -112,7 +112,7 @@ internal Item() /// /// The duration. /// - [JsonProperty("duration")] + [JsonProperty("duration", NullValueHandling = NullValueHandling.Include)] public Duration Duration { get; set; } /// From 542a3b0da752c09835892f1399447d92549862cd Mon Sep 17 00:00:00 2001 From: Ahmed Zaki Date: Fri, 29 Sep 2023 17:25:47 +0300 Subject: [PATCH 7/7] Added integration test for task Duration property. --- .../Services/ItemsServiceTests.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Todoist.Net.Tests/Services/ItemsServiceTests.cs b/src/Todoist.Net.Tests/Services/ItemsServiceTests.cs index 5a88b2e..68eba51 100644 --- a/src/Todoist.Net.Tests/Services/ItemsServiceTests.cs +++ b/src/Todoist.Net.Tests/Services/ItemsServiceTests.cs @@ -197,5 +197,37 @@ public void CreateNewItem_DueDateIsLocal_DueDateNotChanged() client.Items.DeleteAsync(item.Id).Wait(); } + + + [Fact] + [Trait(Constants.TraitName, Constants.IntegrationPremiumTraitValue)] + public void CreateItemClearDurationAndDelete_Success() + { + var client = TodoistClientFactory.Create(_outputHelper); + + var item = new Item("duration task") + { + DueDate = new DueDate("22 Dec 2021 at 9:15", language: Language.English), + Duration = new Duration(45, DurationUnit.Minute) + }; + client.Items.AddAsync(item).Wait(); + + var itemInfo = client.Items.GetAsync(item.Id).Result; + + Assert.True(itemInfo.Item.Content == item.Content); + Assert.Equal("2021-12-22T09:15:00", itemInfo.Item.DueDate.StringDate); + + Assert.Equal(item.Duration.Amount, itemInfo.Item.Duration.Amount); + Assert.Equal(item.Duration.Unit, itemInfo.Item.Duration.Unit); + + itemInfo.Item.Duration = null; + client.Items.UpdateAsync(itemInfo.Item).Wait(); + + itemInfo = client.Items.GetAsync(item.Id).Result; + Assert.Null(itemInfo.Item.Duration); + + client.Items.DeleteAsync(item.Id).Wait(); + } + } }