• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rwjdk / TrelloDotNet / 16099027285

06 Jul 2025 12:29PM UTC coverage: 79.551% (-0.4%) from 79.911%
16099027285

push

github

rwjdk
Version 2.1.0

2539 of 3464 branches covered (73.3%)

Branch coverage included in aggregate %.

0 of 22 new or added lines in 3 files covered. (0.0%)

11 existing lines in 3 files now uncovered.

4479 of 5358 relevant lines covered (83.59%)

113.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.75
/src/TrelloDotNet/TrelloClient.Cards.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Globalization;
4
using System.Linq;
5
using System.Text.Json;
6
using System.Threading;
7
using System.Threading.Tasks;
8
using TrelloDotNet.Control;
9
using TrelloDotNet.Model;
10
using TrelloDotNet.Model.Options;
11
using TrelloDotNet.Model.Options.AddCardFromTemplateOptions;
12
using TrelloDotNet.Model.Options.AddCardOptions;
13
using TrelloDotNet.Model.Options.AddCardToInboxOptions;
14
using TrelloDotNet.Model.Options.CopyCardOptions;
15
using TrelloDotNet.Model.Options.GetCardOptions;
16
using TrelloDotNet.Model.Options.GetInboxCardOptions;
17
using TrelloDotNet.Model.Options.MirrorCardOptions;
18
using TrelloDotNet.Model.Options.MoveCardToBoardOptions;
19
using TrelloDotNet.Model.Options.MoveCardToListOptions;
20

21
// ReSharper disable UnusedMember.Global
22

23
namespace TrelloDotNet
24
{
25
    public partial class TrelloClient
26
    {
27
        /// <summary>
28
        /// Creates a new card in a specified list with the provided options.
29
        /// </summary>
30
        /// <param name="options">Options for the new card, such as name, list ID, description, dates, checklists, attachments, labels, members, custom fields, and cover. <see cref="AddCardOptions"/></param>
31
        /// <param name="cancellationToken">Cancellation Token</param>
32
        /// <returns>The created <see cref="Card"/> object with all requested properties set.</returns>
33
        public async Task<Card> AddCardAsync(AddCardOptions options, CancellationToken cancellationToken = default)
34
        {
35
            if (options == null)
129!
36
            {
37
                throw new ArgumentNullException(nameof(options), $"{nameof(AddCardOptions)} cannot be null");
×
38
            }
39

40
            if (string.IsNullOrWhiteSpace(options.ListId))
129!
41
            {
42
                throw new TrelloApiException("No ListId provided in options (Mandatory)", string.Empty);
×
43
            }
44

45
            var input = new Card
129✔
46
            {
129✔
47
                ListId = options.ListId,
129✔
48
                Name = options.Name,
129✔
49
                Description = options.Description,
129✔
50
                IsTemplate = options.IsTemplate,
129✔
51
                DueComplete = options.DueComplete
129✔
52
            };
129✔
53

54
            if (options.Start.HasValue)
129✔
55
            {
56
                input.Start = options.Start.Value;
16✔
57
            }
58

59
            if (options.Due.HasValue)
129✔
60
            {
61
                input.Due = options.Due.Value;
23✔
62
            }
63

64
            if (options.Position.HasValue)
129!
65
            {
66
                input.Position = options.Position.Value;
×
67
            }
68
            else
69
            {
70
                input.NamedPosition = options.NamedPosition;
129✔
71
            }
72

73
            if (options.LabelIds != null)
129✔
74
            {
75
                input.LabelIds = options.LabelIds.Distinct().ToList();
129✔
76
            }
77

78
            if (options.MemberIds != null)
129✔
79
            {
80
                input.MemberIds = options.MemberIds.Distinct().ToList();
129✔
81
            }
82

83
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(input);
129✔
84

85
            _queryParametersBuilder.AdjustForNamedPosition(parameters, input.NamedPosition);
129✔
86
            Card addedCard = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
129✔
87

88
            bool needGet = false;
129✔
89
            var getCardOptions = new GetCardOptions();
129✔
90
            if (options.Checklists != null)
129✔
91
            {
92
                needGet = true;
129✔
93
                getCardOptions.IncludeChecklists = true;
129✔
94
                foreach (Checklist checklist in options.Checklists)
262✔
95
                {
96
                    checklist.NamedPosition = NamedPosition.Bottom;
2✔
97
                    await AddChecklistAsync(addedCard.Id, checklist, cancellationToken: cancellationToken);
2✔
98
                }
99
            }
100

101
            if (options.AttachmentFileUploads != null)
129✔
102
            {
103
                needGet = true;
129✔
104
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
129✔
105
                foreach (AttachmentFileUpload fileUpload in options.AttachmentFileUploads)
260✔
106
                {
107
                    await AddAttachmentToCardAsync(addedCard.Id, fileUpload, cancellationToken: cancellationToken);
1✔
108
                }
109
            }
110

111
            if (options.AttachmentUrlLinks != null)
129✔
112
            {
113
                needGet = true;
129✔
114
                getCardOptions.IncludeAttachments = GetCardOptionsIncludeAttachments.True;
129✔
115
                foreach (AttachmentUrlLink urlLink in options.AttachmentUrlLinks)
260✔
116
                {
117
                    await AddAttachmentToCardAsync(addedCard.Id, urlLink, cancellationToken: cancellationToken);
1✔
118
                }
119
            }
120

121
            if (options.Cover != null)
129✔
122
            {
123
                needGet = true;
2✔
124
                await AddCoverToCardAsync(addedCard.Id, options.Cover, cancellationToken);
2✔
125
            }
126

127
            // ReSharper disable once InvertIf
128
            if (options.CustomFields != null)
129✔
129
            {
130
                needGet = true;
129✔
131
                getCardOptions.IncludeCustomFieldItems = true;
129✔
132
                foreach (var customField in options.CustomFields)
270✔
133
                {
134
                    switch (customField.Field.Type)
6✔
135
                    {
136
                        case CustomFieldType.Checkbox:
137
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (bool)customField.Value, cancellationToken);
1✔
138
                            break;
1✔
139
                        case CustomFieldType.Date:
140
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (DateTimeOffset)customField.Value, cancellationToken);
1✔
141
                            break;
1✔
142
                        case CustomFieldType.List:
143
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (CustomFieldOption)customField.Value, cancellationToken);
1✔
144
                            break;
1✔
145
                        case CustomFieldType.Number:
146
                            switch (customField.Value)
2✔
147
                            {
148
                                case int intValue:
149
                                    await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, intValue, cancellationToken);
1✔
150
                                    break;
1✔
151
                                case decimal decimalValue:
152
                                    await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, decimalValue, cancellationToken);
1✔
153
                                    break;
1✔
154
                            }
155

156
                            break;
157
                        case CustomFieldType.Text:
158
                        default:
159
                            await UpdateCustomFieldValueOnCardAsync(addedCard.Id, customField.Field, (string)customField.Value, cancellationToken);
1✔
160
                            break;
161
                    }
162
                }
163
            }
164

165
            return needGet ? await GetCardAsync(addedCard.Id, getCardOptions, cancellationToken) : addedCard;
129!
166
        }
129✔
167

168
        /// <summary>
169
        /// Creates a new card in the inbox list of the member who owns the Trello token, using the provided options.
170
        /// </summary>
171
        /// <param name="options">Options for the new inbox card, such as name, description, dates, checklists, attachments, and cover. <see cref="AddCardToInboxOptions"/></param>
172
        /// <param name="cancellationToken">CancellationToken</param>
173
        /// <returns>The created <see cref="Card"/> object in the inbox list.</returns>
174
        public async Task<Card> AddCardToInboxAsync(AddCardToInboxOptions options, CancellationToken cancellationToken = default)
175
        {
176
            if (options == null)
×
177
            {
178
                throw new ArgumentNullException(nameof(options), $"{nameof(AddCardToInboxOptions)} cannot be null");
×
179
            }
180

181
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
182
            if (inbox == null)
×
183
            {
184
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
185
            }
186

187
            Card input = new Card
×
188
            {
×
189
                ListId = inbox.ListId,
×
190
                Name = options.Name,
×
191
                Description = options.Description,
×
192
                LabelIds = new List<string>(),
×
193
                MemberIds = new List<string>(),
×
194
            };
×
195

196
            if (options.Start.HasValue)
×
197
            {
198
                input.Start = options.Start.Value;
×
199
            }
200

201
            if (options.Due.HasValue)
×
202
            {
203
                input.Due = options.Due.Value;
×
204
            }
205

206
            input.DueComplete = options.DueComplete;
×
207

208
            if (options.Position.HasValue)
×
209
            {
210
                input.Position = options.Position.Value;
×
211
            }
212
            else
213
            {
214
                input.NamedPosition = options.NamedPosition;
×
215
            }
216

217
            QueryParameter[] parameters = _queryParametersBuilder.GetViaQueryParameterAttributes(input);
×
218
            Card addedCard = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
×
219

220
            if (options.Checklists != null)
×
221
            {
222
                foreach (Checklist checklist in options.Checklists)
×
223
                {
224
                    checklist.NamedPosition = NamedPosition.Bottom;
×
225
                    await AddChecklistAsync(addedCard.Id, checklist, cancellationToken: cancellationToken);
×
226
                }
227
            }
228

229
            if (options.AttachmentFileUploads != null)
×
230
            {
231
                foreach (AttachmentFileUpload fileUpload in options.AttachmentFileUploads)
×
232
                {
233
                    await AddAttachmentToCardAsync(addedCard.Id, fileUpload, cancellationToken: cancellationToken);
×
234
                }
235
            }
236

237
            if (options.AttachmentUrlLinks != null)
×
238
            {
239
                foreach (AttachmentUrlLink urlLink in options.AttachmentUrlLinks)
×
240
                {
241
                    await AddAttachmentToCardAsync(addedCard.Id, urlLink, cancellationToken: cancellationToken);
×
242
                }
243
            }
244

245
            if (options.Cover != null)
×
246
            {
247
                await AddCoverToCardAsync(addedCard.Id, options.Cover, cancellationToken);
×
248
            }
249

250
            return addedCard;
×
251
        }
×
252

253
        /// <summary>
254
        /// Creates a new card based on a template card, copying selected properties and content from the template.
255
        /// </summary>
256
        /// <param name="options">Options for creating the card from a template, including template card ID, target list, name, position, and what to keep. <see cref="AddCardFromTemplateOptions"/></param>
257
        /// <param name="cancellationToken">Cancellation Token</param>
258
        /// <returns>The newly created <see cref="Card"/> based on the template.</returns>
259
        public async Task<Card> AddCardFromTemplateAsync(AddCardFromTemplateOptions options, CancellationToken cancellationToken = default)
260
        {
261
            if (options == null)
1!
262
            {
263
                throw new ArgumentNullException(nameof(options), $"{nameof(AddCardFromTemplateOptions)} cannot be null");
×
264
            }
265

266
            var nameOnNewCard = !string.IsNullOrWhiteSpace(options.Name)
1!
267
                ? options.Name
1✔
268
                : (await GetCardAsync(options.SourceTemplateCardId, new GetCardOptions
1✔
269
                {
1✔
270
                    CardFields = new CardFields(CardFieldsType.Name)
1✔
271
                }, cancellationToken)).Name;
1✔
272

273
            string position = "bottom";
1✔
274

275
            if (options.Position.HasValue)
1!
276
            {
277
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
278
            }
279

280
            if (options.NamedPosition.HasValue)
1✔
281
            {
282
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
283
            }
284

285
            string keepFromSource = "all";
1✔
286
            if (options.Keep.HasValue)
1✔
287
            {
288
                AddCardFromTemplateOptionsToKeep keep = options.Keep.Value;
1✔
289

290
                if (keep.HasFlag(AddCardFromTemplateOptionsToKeep.All))
1!
291
                {
292
                    keepFromSource = "all";
1✔
293
                }
294
                else
295
                {
296
                    var keepStrings = new List<string>();
×
297
                    var enumValues = Enum.GetValues(typeof(AddCardFromTemplateOptionsToKeep)).Cast<AddCardFromTemplateOptionsToKeep>().ToList();
×
298
                    foreach (AddCardFromTemplateOptionsToKeep toKeep in enumValues.Where(x => x != AddCardFromTemplateOptionsToKeep.All))
×
299
                    {
300
                        if (keep.HasFlag(toKeep))
×
301
                        {
302
                            keepStrings.Add(toKeep.GetJsonPropertyName());
×
303
                        }
304
                    }
305

306
                    keepFromSource = string.Join(",", keepStrings);
×
307
                }
308
            }
309

310
            QueryParameter[] parameters =
1✔
311
            {
1✔
312
                new QueryParameter("name", nameOnNewCard),
1✔
313
                new QueryParameter("idList", options.TargetListId),
1✔
314
                new QueryParameter("pos", position),
1✔
315
                new QueryParameter("idCardSource", options.SourceTemplateCardId),
1✔
316
                new QueryParameter("keepFromSource", keepFromSource)
1✔
317
            };
1✔
318
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
1✔
319
        }
1✔
320

321
        /// <summary>
322
        /// Adds a separator card (with name "---" and cardRole "separator") to the specified list at the given position.
323
        /// </summary>
324
        /// <param name="listId">The ID of the list to add the separator card to.</param>
325
        /// <param name="position">The position in the list to add the separator card (0 for top).</param>
326
        /// <param name="cancellationToken">Cancellation Token</param>
327
        /// <returns>The created separator <see cref="Card"/>.</returns>
328
        public async Task<Card> AddSeparatorCardAsync(string listId, decimal position, CancellationToken cancellationToken = default)
329
        {
NEW
330
            if (position == 0)
×
331
            {
NEW
332
                return await AddSeparatorCardAsync(listId, NamedPosition.Top, cancellationToken);
×
333
            }
334

NEW
335
            List<QueryParameter> parameters = new List<QueryParameter>
×
NEW
336
            {
×
NEW
337
                new QueryParameter(CardFieldsType.ListId, listId),
×
NEW
338
                new QueryParameter(CardFieldsType.Name, "---"),
×
NEW
339
                new QueryParameter(CardFieldsType.Position, position),
×
NEW
340
                new QueryParameter(CardFieldsType.CardRole, "separator")
×
NEW
341
            };
×
NEW
342
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters.ToArray());
×
NEW
343
        }
×
344

345
        /// <summary>
346
        /// Adds a separator card (with name "---" and cardRole "separator") to the specified list at the given position.
347
        /// </summary>
348
        /// <param name="listId">The ID of the list to add the separator card to.</param>
349
        /// <param name="position">The position in the list to add the separator card.</param>
350
        /// <param name="cancellationToken">Cancellation Token</param>
351
        /// <returns>The created separator <see cref="Card"/>.</returns>
352
        public async Task<Card> AddSeparatorCardAsync(string listId, NamedPosition position, CancellationToken cancellationToken = default)
353
        {
NEW
354
            List<QueryParameter> parameters = new List<QueryParameter>
×
NEW
355
            {
×
NEW
356
                new QueryParameter(CardFieldsType.ListId, listId),
×
NEW
357
                new QueryParameter(CardFieldsType.Name, "---"),
×
NEW
358
                new QueryParameter(CardFieldsType.Position, position == NamedPosition.Top ? "top" : "bottom"),
×
NEW
359
                new QueryParameter(CardFieldsType.CardRole, "separator")
×
NEW
360
            };
×
NEW
361
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters.ToArray());
×
NEW
362
        }
×
363

364
        /// <summary>
365
        /// Creates a copy of an existing card, with options to specify which properties to keep or override.
366
        /// </summary>
367
        /// <param name="options">Options for copying the card, including source card ID, target list, name, position, and what to keep. <see cref="CopyCardOptions"/></param>
368
        /// <param name="cancellationToken">Cancellation Token</param>
369
        /// <returns>The copied <see cref="Card"/>.</returns>
370
        public async Task<Card> CopyCardAsync(CopyCardOptions options, CancellationToken cancellationToken = default)
371
        {
372
            if (options == null)
1!
373
            {
374
                throw new ArgumentNullException(nameof(options), $"{nameof(CopyCardOptions)} cannot be null");
×
375
            }
376

377
            var nameOnNewCard = !string.IsNullOrWhiteSpace(options.Name)
1!
378
                ? options.Name
1✔
379
                : (await GetCardAsync(options.SourceCardId, new GetCardOptions
1✔
380
                {
1✔
381
                    CardFields = new CardFields(CardFieldsType.Name)
1✔
382
                }, cancellationToken)).Name;
1✔
383

384
            string position = "bottom";
1✔
385

386
            if (options.Position.HasValue)
1!
387
            {
388
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
389
            }
390

391
            if (options.NamedPosition.HasValue)
1✔
392
            {
393
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
394
            }
395

396
            string keepFromSource = "all";
1✔
397
            if (options.Keep.HasValue)
1✔
398
            {
399
                CopyCardOptionsToKeep keep = options.Keep.Value;
1✔
400

401
                if (keep.HasFlag(CopyCardOptionsToKeep.All))
1!
402
                {
403
                    keepFromSource = "all";
×
404
                }
405
                else
406
                {
407
                    var keepStrings = new List<string>();
1✔
408
                    var enumValues = Enum.GetValues(typeof(CopyCardOptionsToKeep)).Cast<CopyCardOptionsToKeep>().ToList();
1✔
409
                    foreach (CopyCardOptionsToKeep toKeep in enumValues.Where(x => x != CopyCardOptionsToKeep.All))
30✔
410
                    {
411
                        if (keep.HasFlag(toKeep))
9✔
412
                        {
413
                            keepStrings.Add(toKeep.GetJsonPropertyName());
3✔
414
                        }
415
                    }
416

417
                    keepFromSource = string.Join(",", keepStrings);
1✔
418
                }
419
            }
420

421
            QueryParameter[] parameters =
1✔
422
            {
1✔
423
                new QueryParameter("name", nameOnNewCard),
1✔
424
                new QueryParameter("idList", options.TargetListId),
1✔
425
                new QueryParameter("pos", position),
1✔
426
                new QueryParameter("idCardSource", options.SourceCardId),
1✔
427
                new QueryParameter("keepFromSource", keepFromSource)
1✔
428
            };
1✔
429
            return await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters);
1✔
430
        }
1✔
431

432
        /// <summary>
433
        /// Creates a mirror card in a target list, linking it to the source card for synchronization.
434
        /// </summary>
435
        /// <param name="options">Options for mirroring the card, including source card ID, target list, and position. <see cref="MirrorCardOptions"/></param>
436
        /// <param name="cancellationToken">Cancellation Token</param>
437
        /// <returns>The created mirror <see cref="Card"/>.</returns>
438
        public async Task<Card> MirrorCardAsync(MirrorCardOptions options, CancellationToken cancellationToken = default)
439
        {
440
            if (options == null)
1!
441
            {
442
                throw new ArgumentNullException(nameof(options), $"{nameof(MirrorCardOptions)} cannot be null");
×
443
            }
444

445
            //Get Source-Card as we need the ShortUrl to make the Card Mirror magic Happen
446
            Card sourceCard = await GetCardAsync(options.SourceCardId, new GetCardOptions
1✔
447
            {
1✔
448
                CardFields = new CardFields(CardFieldsType.ShortUrl)
1✔
449
            }, cancellationToken);
1✔
450

451
            string position = "bottom";
1✔
452

453
            if (options.Position.HasValue)
1!
454
            {
455
                position = options.Position.Value.ToString(CultureInfo.InvariantCulture);
×
456
            }
457

458
            if (options.NamedPosition.HasValue)
1✔
459
            {
460
                position = options.NamedPosition.Value == NamedPosition.Bottom ? "bottom" : "top";
1!
461
            }
462

463
            var parameters = new List<QueryParameter>
1✔
464
            {
1✔
465
                new QueryParameter("idList", options.TargetListId),
1✔
466
                new QueryParameter("name", sourceCard.ShortUrl),
1✔
467
                new QueryParameter("isTemplate", false),
1✔
468
                new QueryParameter("closed", false),
1✔
469
                new QueryParameter("pos", position),
1✔
470
                new QueryParameter("cardRole", "mirror"),
1✔
471
            };
1✔
472

473
            var result = await _apiRequestController.Post<Card>($"{UrlPaths.Cards}", cancellationToken, parameters.ToArray());
1✔
474
            return result;
1✔
475
        }
1✔
476

477
        /// <summary>
478
        /// Archives (closes) the specified card, removing it from the active list but not deleting it permanently.
479
        /// </summary>
480
        /// <param name="cardId">The ID of the card to archive.</param>
481
        /// <param name="cancellationToken">Cancellation Token</param>
482
        /// <returns>The archived <see cref="Card"/>.</returns>
483
        public async Task<Card> ArchiveCardAsync(string cardId, CancellationToken cancellationToken = default)
484
        {
485
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", true));
1✔
486
        }
1✔
487

488
        /// <summary>
489
        /// Reopens an archived card, making it active and visible on the board again.
490
        /// </summary>
491
        /// <param name="cardId">The ID of the card to reopen.</param>
492
        /// <param name="cancellationToken">Cancellation Token</param>
493
        /// <returns>The reopened <see cref="Card"/>.</returns>
494
        public async Task<Card> ReOpenCardAsync(string cardId, CancellationToken cancellationToken = default)
495
        {
496
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, new QueryParameter("closed", false));
1✔
497
        }
1✔
498

499
        /// <summary>
500
        /// Updates one or more specific fields on a card, such as name, description, dates, members, labels, or cover.
501
        /// </summary>
502
        /// <param name="cardId">The ID of the card to update.</param>
503
        /// <param name="valuesToUpdate">A list of updates to apply to the card. <see cref="CardUpdate"/></param>
504
        /// <param name="cancellationToken">CancellationToken</param>
505
        /// <returns>The updated <see cref="Card"/>.</returns>
506
        public async Task<Card> UpdateCardAsync(string cardId, List<CardUpdate> valuesToUpdate, CancellationToken cancellationToken = default)
507
        {
508
            var parameters = valuesToUpdate.Select(x => x.ToQueryParameter()).ToList();
207✔
509
            QueryParameter coverParameter = parameters.FirstOrDefault(x => x.Name == "cover");
187✔
510
            if (coverParameter != null && !string.IsNullOrWhiteSpace(coverParameter.GetRawStringValue()))
67✔
511
            {
512
                //Special Cover Card
513
                parameters.Remove(coverParameter);
5✔
514
                CardCover cover = JsonSerializer.Deserialize<CardCover>(coverParameter.GetRawStringValue());
5✔
515
                var payload = GeneratePayloadForCoverUpdate(cover, parameters);
5✔
516
                return await _apiRequestController.PutWithJsonPayload<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, payload, parameters.ToArray());
5✔
517
            }
518

519
            return await _apiRequestController.Put<Card>($"{UrlPaths.Cards}/{cardId}", cancellationToken, parameters.ToArray());
62✔
520
        }
67✔
521

522
        /// <summary>
523
        /// Archives all cards in the specified list.
524
        /// </summary>
525
        /// <param name="listId">The ID of the list whose cards should be archived.</param>
526
        /// <param name="cancellationToken">Cancellation Token</param>
527
        public async Task ArchiveAllCardsInListAsync(string listId, CancellationToken cancellationToken = default)
528
        {
529
            await _apiRequestController.Post<List>($"{UrlPaths.Lists}/{listId}/archiveAllCards", cancellationToken);
1✔
530
        }
1✔
531

532
        /// <summary>
533
        /// Moves all cards from one list to another list (on same or other Board)
534
        /// </summary>
535
        /// <param name="currentListId">The ID of the list whose cards should be moved.</param>
536
        /// <param name="newListId">The ID of the destination list.</param>
537
        /// <param name="cancellationToken">Cancellation Token</param>
538
        public async Task MoveAllCardsInListAsync(string currentListId, string newListId, CancellationToken cancellationToken = default)
539
        {
540
            var newList = await GetListAsync(newListId, cancellationToken); //Get the new list's BoardId so the user do not need to provide it.
1✔
541
            await _apiRequestController.Post($"{UrlPaths.Lists}/{currentListId}/moveAllCards", cancellationToken,
1✔
542
                0,
1✔
543
                new QueryParameter("idBoard", newList.BoardId),
1✔
544
                new QueryParameter("idList", newListId)
1✔
545
            );
1✔
546
        }
1✔
547

548
        /// <summary>
549
        /// Permanently deletes a card. <b>Warning: This action cannot be undone. Use <see cref="ArchiveCardAsync"/> for non-permanent removal.</b>
550
        /// </summary>
551
        /// <param name="cardId">The ID of the card to delete.</param>
552
        /// <param name="cancellationToken">Cancellation Token</param>
553
        public async Task DeleteCardAsync(string cardId, CancellationToken cancellationToken = default)
554
        {
555
            await _apiRequestController.Delete($"{UrlPaths.Cards}/{cardId}", cancellationToken, 0);
4✔
556
        }
4✔
557

558
        /// <summary>
559
        /// Retrieves a card by its ID, including all default properties.
560
        /// </summary>
561
        /// <param name="cardId">The ID of the card to retrieve.</param>
562
        /// <param name="cancellationToken">Cancellation Token</param>
563
        /// <returns>The requested <see cref="Card"/>.</returns>
564
        public async Task<Card> GetCardAsync(string cardId, CancellationToken cancellationToken = default)
565
        {
566
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken);
68✔
567
        }
68✔
568

569
        /// <summary>
570
        /// Retrieves a card by its ID, with options to include or exclude specific fields and nested data.
571
        /// </summary>
572
        /// <param name="cardId">The ID of the card to retrieve.</param>
573
        /// <param name="options">Options specifying which fields and nested data to include. <see cref="GetCardOptions"/></param>
574
        /// <param name="cancellationToken">Cancellation Token</param>
575
        /// <returns>The requested <see cref="Card"/>.</returns>
576
        public async Task<Card> GetCardAsync(string cardId, GetCardOptions options, CancellationToken cancellationToken = default)
577
        {
578
            if (options == null)
198!
579
            {
580
                throw new ArgumentNullException(nameof(options), $"{nameof(GetCardOptions)} cannot be null");
×
581
            }
582

583
            return await _apiRequestController.Get<Card>(GetUrlBuilder.GetCard(cardId), cancellationToken, options.GetParameters(false));
198✔
584
        }
198✔
585

586
        /// <summary>
587
        /// Retrieves all open cards on all un-archived lists on a board.
588
        /// </summary>
589
        /// <param name="boardId">The ID of the board (long or short form).</param>
590
        /// <param name="cancellationToken">CancellationToken</param>
591
        /// <returns>A list of <see cref="Card"/> objects on the board.</returns>
592
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, CancellationToken cancellationToken = default)
593
        {
594
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken);
18✔
595
        }
18✔
596

597
        /// <summary>
598
        /// Retrieves all cards on a board, with options to filter, include archived cards, and specify which fields to include.
599
        /// </summary>
600
        /// <param name="boardId">The ID of the board (long or short form).</param>
601
        /// <param name="options">Options specifying which cards and fields to include. <see cref="GetCardOptions"/></param>
602
        /// <param name="cancellationToken">CancellationToken</param>
603
        /// <returns>A list of <see cref="Card"/> objects on the board.</returns>
604
        public async Task<List<Card>> GetCardsOnBoardAsync(string boardId, GetCardOptions options, CancellationToken cancellationToken = default)
605
        {
606
            if (options == null)
251!
607
            {
608
                throw new ArgumentNullException(nameof(options), $"{nameof(GetCardOptions)} cannot be null");
×
609
            }
610

611
            options.AdjustFieldsBasedOnSelectedOptions();
251✔
612
            if (options.IncludeList)
251✔
613
            {
614
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
25✔
615
                {
616
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
617
                    {
1✔
618
                        "idList"
1✔
619
                    }).ToArray();
1✔
620
                }
621
            }
622

623
            if (options.IncludeBoard)
251✔
624
            {
625
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
626
                {
627
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
628
                    {
1✔
629
                        "idBoard"
1✔
630
                    }).ToArray();
1✔
631
                }
632
            }
633

634
            List<Card> cards;
635
            if (options.Filter.HasValue)
251✔
636
            {
637
                cards = await _apiRequestController.Get<List<Card>>($"{GetUrlBuilder.GetCardsOnBoard(boardId)}/{options.Filter.Value.GetJsonPropertyName()}", cancellationToken, options.GetParameters(true));
2✔
638
            }
639
            else
640
            {
641
                cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsOnBoard(boardId), cancellationToken, options.GetParameters(true));
249✔
642
            }
643

644
            if (options.IncludeList)
251✔
645
            {
646
                var lists = await GetListsOnBoardAsync(boardId, cancellationToken);
25✔
647
                foreach (Card card in cards)
254✔
648
                {
649
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
559✔
650
                }
651
            }
652

653
            if (options.IncludeBoard)
251✔
654
            {
655
                var board = await GetBoardAsync(boardId, cancellationToken);
1✔
656
                foreach (Card card in cards)
62✔
657
                {
658
                    card.Board = board;
30✔
659
                }
660
            }
661

662
            cards = FilterCards(cards, options.FilterConditions);
251✔
663
            return OrderCards(cards, options.OrderBy);
245✔
664
        }
245✔
665

666
        /// <summary>
667
        /// Retrieves all open cards on a specific list.
668
        /// </summary>
669
        /// <param name="listId">The ID of the list.</param>
670
        /// <param name="cancellationToken">Cancellation Token</param>
671
        /// <returns>A list of <see cref="Card"/> objects in the list.</returns>
672
        public async Task<List<Card>> GetCardsInListAsync(string listId, CancellationToken cancellationToken = default)
673
        {
674
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken);
13✔
675
        }
13✔
676

677
        /// <summary>
678
        /// Retrieves all open cards on a specific list, with options to include board and list details.
679
        /// </summary>
680
        /// <param name="listId">The ID of the list.</param>
681
        /// <param name="options">Options specifying which fields and nested data to include. <see cref="GetCardOptions"/></param>
682
        /// <param name="cancellationToken">Cancellation Token</param>
683
        /// <returns>A list of <see cref="Card"/> objects in the list.</returns>
684
        public async Task<List<Card>> GetCardsInListAsync(string listId, GetCardOptions options, CancellationToken cancellationToken = default)
685
        {
686
            if (options == null)
1!
687
            {
688
                throw new ArgumentNullException(nameof(options), $"{nameof(GetCardOptions)} cannot be null");
×
689
            }
690

691
            options.AdjustFieldsBasedOnSelectedOptions();
1✔
692
            if (options.IncludeBoard)
1✔
693
            {
694
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
695
                {
696
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
697
                    {
1✔
698
                        "idBoard"
1✔
699
                    }).ToArray();
1✔
700
                }
701
            }
702

703
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsInList(listId), cancellationToken, options.GetParameters(true));
1✔
704
            if (options.IncludeList)
1✔
705
            {
706
                var list = await GetListAsync(listId, cancellationToken);
1✔
707
                foreach (Card card in cards)
8✔
708
                {
709
                    card.ListId = listId;
3✔
710
                    card.List = list;
3✔
711
                }
712
            }
713

714
            if (options.IncludeBoard && cards.Count > 0)
1✔
715
            {
716
                var board = await GetBoardAsync(cards[0].BoardId, cancellationToken);
1✔
717
                foreach (Card card in cards)
8✔
718
                {
719
                    card.Board = board;
3✔
720
                }
721
            }
722

723
            cards = FilterCards(cards, options.FilterConditions);
1✔
724
            return OrderCards(cards, options.OrderBy);
1✔
725
        }
1✔
726

727
        /// <summary>
728
        /// Retrieves all cards in the inbox of the current member.
729
        /// </summary>
730
        /// <param name="cancellationToken">CancellationToken</param>
731
        /// <returns>A list of <see cref="Card"/> objects in the inbox.</returns>
732
        public async Task<List<Card>> GetCardsInInboxAsync(CancellationToken cancellationToken = default)
733
        {
734
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
735
            if (inbox == null)
×
736
            {
737
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
738
            }
739

740
            return await GetCardsOnBoardAsync(inbox.BoardId, cancellationToken);
×
741
        }
×
742

743
        /// <summary>
744
        /// Retrieves all cards in the inbox of the current member, with options to include specific fields and nested data.
745
        /// </summary>
746
        /// <param name="options">Options specifying which fields and nested data to include. <see cref="GetInboxCardOptions"/></param>
747
        /// <param name="cancellationToken">CancellationToken</param>
748
        /// <returns>A list of <see cref="Card"/> objects in the inbox.</returns>
749
        public async Task<List<Card>> GetCardsInInboxAsync(GetInboxCardOptions options, CancellationToken cancellationToken = default)
750
        {
751
            if (options == null)
×
752
            {
753
                throw new ArgumentNullException(nameof(options), $"{nameof(GetInboxCardOptions)} cannot be null");
×
754
            }
755

756
            TokenMemberInbox inbox = await GetTokenMemberInboxAsync(cancellationToken);
×
757
            if (inbox == null)
×
758
            {
759
                throw new TrelloApiException("Could not find your inbox", string.Empty);
×
760
            }
761

762
            GetCardOptions getCardOptions = options.ToCardOptions();
×
763
            var cards = await GetCardsOnBoardAsync(inbox.BoardId, getCardOptions, cancellationToken);
×
764
            cards = FilterCards(cards, getCardOptions.FilterConditions);
×
765
            return OrderCards(cards, getCardOptions.OrderBy);
×
766
        }
×
767

768
        /// <summary>
769
        /// Retrieves all cards assigned to a member across multiple boards.
770
        /// </summary>
771
        /// <param name="memberId">The ID of the member.</param>
772
        /// <param name="cancellationToken">Cancellation Token</param>
773
        /// <returns>A list of <see cref="Card"/> objects assigned to the member.</returns>
774
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, CancellationToken cancellationToken = default)
775
        {
776
            return await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken);
1✔
777
        }
1✔
778

779
        /// <summary>
780
        /// Retrieves all cards assigned to a member across multiple boards, with options to include specific fields and nested data.
781
        /// </summary>
782
        /// <param name="memberId">The ID of the member.</param>
783
        /// <param name="options">Options specifying which fields and nested data to include. <see cref="GetCardOptions"/></param>
784
        /// <param name="cancellationToken">Cancellation Token</param>
785
        /// <returns>A list of <see cref="Card"/> objects assigned to the member.</returns>
786
        public async Task<List<Card>> GetCardsForMemberAsync(string memberId, GetCardOptions options, CancellationToken cancellationToken = default)
787
        {
788
            if (options == null)
1!
789
            {
790
                throw new ArgumentNullException(nameof(options), $"{nameof(GetCardOptions)} cannot be null");
×
791
            }
792

793
            options.AdjustFieldsBasedOnSelectedOptions();
1✔
794
            if (options.IncludeList)
1✔
795
            {
796
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idList"))
1✔
797
                {
798
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
799
                    {
1✔
800
                        "idList"
1✔
801
                    }).ToArray();
1✔
802
                }
803

804
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1✔
805
                {
806
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
1✔
807
                    {
1✔
808
                        "idBoard"
1✔
809
                    }).ToArray();
1✔
810
                }
811
            }
812

813
            if (options.IncludeBoard)
1✔
814
            {
815
                if (options.CardFields != null && !options.CardFields.Fields.Contains("idBoard"))
1!
816
                {
817
                    options.CardFields.Fields = options.CardFields.Fields.Union(new List<string>
×
818
                    {
×
819
                        "idBoard"
×
820
                    }).ToArray();
×
821
                }
822
            }
823

824
            var cards = await _apiRequestController.Get<List<Card>>(GetUrlBuilder.GetCardsForMember(memberId), cancellationToken, options.GetParameters(true));
1✔
825
            if (options.IncludeList)
1✔
826
            {
827
                var boardsToGetListsFor = cards.Select(x => x.BoardId).Distinct().ToArray();
18✔
828
                List<List> lists = new List<List>();
1✔
829
                foreach (var boardId in boardsToGetListsFor)
24✔
830
                {
831
                    lists.AddRange(await GetListsOnBoardAsync(boardId, cancellationToken));
11✔
832
                }
833

834
                foreach (Card card in cards)
36✔
835
                {
836
                    card.List = lists.FirstOrDefault(x => x.Id == card.ListId);
764✔
837
                }
838
            }
1✔
839

840
            if (options.IncludeBoard)
1✔
841
            {
842
                var boardIds = cards.Select(x => x.BoardId).Distinct().ToList();
18✔
843
                var boards = await GetBoardsAsync(boardIds, cancellationToken);
1✔
844
                foreach (Card card in cards)
36✔
845
                {
846
                    card.Board = boards.FirstOrDefault(x => x.Id == card.BoardId);
135✔
847
                }
848
            }
849

850
            cards = FilterCards(cards, options.FilterConditions);
1✔
851
            return OrderCards(cards, options.OrderBy);
1✔
852
        }
1✔
853

854
        /// <summary>
855
        /// Sets the due date and completion status on a card.
856
        /// </summary>
857
        /// <param name="cardId">The ID of the card.</param>
858
        /// <param name="dueDate">The due date to set (in UTC).</param>
859
        /// <param name="dueComplete">Whether the card is complete.</param>
860
        /// <param name="cancellationToken">Cancellation Token</param>
861
        /// <returns>The updated <see cref="Card"/>.</returns>
862
        public async Task<Card> SetDueDateOnCardAsync(string cardId, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
863
        {
864
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
865
            {
1✔
866
                CardUpdate.DueDate(dueDate),
1✔
867
                CardUpdate.DueComplete(dueComplete)
1✔
868
            }, cancellationToken);
1✔
869
        }
1✔
870

871
        /// <summary>
872
        /// Sets the start date on a card.
873
        /// </summary>
874
        /// <param name="cardId">The ID of the card.</param>
875
        /// <param name="startDate">The start date to set (in UTC).</param>
876
        /// <param name="cancellationToken">Cancellation Token</param>
877
        /// <returns>The updated <see cref="Card"/>.</returns>
878
        public async Task<Card> SetStartDateOnCardAsync(string cardId, DateTimeOffset startDate, CancellationToken cancellationToken = default)
879
        {
880
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
881
            {
1✔
882
                CardUpdate.StartDate(startDate)
1✔
883
            }, cancellationToken);
1✔
884
        }
1✔
885

886
        /// <summary>
887
        /// Sets both the start and due dates, and completion status, on a card.
888
        /// </summary>
889
        /// <param name="cardId">The ID of the card.</param>
890
        /// <param name="startDate">The start date to set (in UTC).</param>
891
        /// <param name="dueDate">The due date to set (in UTC).</param>
892
        /// <param name="dueComplete">Whether the card is complete.</param>
893
        /// <param name="cancellationToken">Cancellation Token</param> 
894
        /// <returns>The updated <see cref="Card"/>.</returns>
895
        public async Task<Card> SetStartDateAndDueDateOnCardAsync(string cardId, DateTimeOffset startDate, DateTimeOffset dueDate, bool dueComplete = false, CancellationToken cancellationToken = default)
896
        {
897
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
898
            {
1✔
899
                CardUpdate.StartDate(startDate),
1✔
900
                CardUpdate.DueDate(dueDate),
1✔
901
                CardUpdate.DueComplete(dueComplete)
1✔
902
            }, cancellationToken);
1✔
903
        }
1✔
904

905
        /// <summary>
906
        /// Moves a card to a new list on the same board.
907
        /// </summary>
908
        /// <param name="cardId">The ID of the card to move.</param>
909
        /// <param name="newListId">The ID of the destination list.</param>
910
        /// <param name="cancellationToken">Cancellation Token</param>
911
        /// <returns>The updated <see cref="Card"/> in the new list.</returns>
912
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, CancellationToken cancellationToken = default)
913
        {
914
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
915
            {
1✔
916
                CardUpdate.List(newListId)
1✔
917
            }, cancellationToken);
1✔
918
        }
1✔
919

920
        /// <summary>
921
        /// Moves a card to a new list on the same board, with additional options for position.
922
        /// </summary>
923
        /// <param name="cardId">The ID of the card to move.</param>
924
        /// <param name="newListId">The ID of the destination list.</param>
925
        /// <param name="options">Options for the move, such as position in the new list. <see cref="MoveCardToListOptions"/></param>
926
        /// <param name="cancellationToken">Cancellation Token</param>
927
        /// <returns>The updated <see cref="Card"/> in the new list.</returns>
928
        public async Task<Card> MoveCardToListAsync(string cardId, string newListId, MoveCardToListOptions options, CancellationToken cancellationToken = default)
929
        {
930
            if (options == null)
1!
931
            {
932
                throw new ArgumentNullException(nameof(options), $"{nameof(MoveCardToListOptions)} cannot be null");
×
933
            }
934

935
            var parameters = new List<CardUpdate> { CardUpdate.List(newListId) };
1✔
936
            if (options.NamedPositionOnNewList.HasValue)
1!
937
            {
938
                parameters.Add(CardUpdate.Position(options.NamedPositionOnNewList.Value));
1✔
939
            }
940
            else if (options.PositionOnNewList.HasValue)
×
941
            {
942
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
943
            }
944

945
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
1✔
946
        }
1✔
947

948
        /// <summary>
949
        /// Moves a card to the top of its current list.
950
        /// </summary>
951
        /// <param name="cardId">The ID of the card to move.</param>
952
        /// <param name="cancellationToken">Cancellation Token</param>
953
        /// <returns>The updated <see cref="Card"/> at the top of the list.</returns>
954
        public async Task<Card> MoveCardToTopOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
955
        {
956
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
957
            {
1✔
958
                CardUpdate.Position(NamedPosition.Top)
1✔
959
            }, cancellationToken);
1✔
960
        }
1✔
961

962
        /// <summary>
963
        /// Moves a card to the bottom of its current list.
964
        /// </summary>
965
        /// <param name="cardId">The ID of the card to move.</param>
966
        /// <param name="cancellationToken">Cancellation Token</param>
967
        /// <returns>The updated <see cref="Card"/> at the bottom of the list.</returns>
968
        public async Task<Card> MoveCardToBottomOfCurrentListAsync(string cardId, CancellationToken cancellationToken = default)
969
        {
970
            return await UpdateCardAsync(cardId, new List<CardUpdate>
1✔
971
            {
1✔
972
                CardUpdate.Position(NamedPosition.Bottom)
1✔
973
            }, cancellationToken);
1✔
974
        }
1✔
975

976
        /// <summary>
977
        /// Moves a card to another board, with options for list, members, labels, and removal of due/start dates.
978
        /// </summary>
979
        /// <param name="cardId">The ID of the card to move.</param>
980
        /// <param name="newBoardId">The ID of the destination board.</param>
981
        /// <param name="options">Additional Options for the move like what list the card should end up on the new board and what happens to labels and members</param>
982
        /// <param name="cancellationToken">Cancellation Token</param>
983
        /// <returns>The updated <see cref="Card"/> on the new board.</returns>
984
        public async Task<Card> MoveCardToBoardAsync(string cardId, string newBoardId, MoveCardToBoardOptions options, CancellationToken cancellationToken = default)
985
        {
986
            if (options == null)
5!
987
            {
988
                throw new ArgumentNullException(nameof(options), "You need to pass an options object to confirm the various options that are involved with moving a card between boards");
×
989
            }
990

991
            List<CardUpdate> parameters = new List<CardUpdate> { CardUpdate.Board(newBoardId) };
5✔
992
            var newListId = options.NewListId;
5✔
993
            if (string.IsNullOrWhiteSpace(newListId))
5!
994
            {
995
                //No list specified, so we need to find the first list on the board
996
                newListId = (await GetListsOnBoardAsync(newBoardId, cancellationToken)).OrderBy(x => x.Position).FirstOrDefault()?.Id;
×
997
            }
998

999
            parameters.Add(CardUpdate.List(newListId));
5✔
1000

1001
            if (options.NamedPositionOnNewList.HasValue)
5!
1002
            {
1003
                parameters.Add(CardUpdate.Position((options.NamedPositionOnNewList.Value)));
5✔
1004
            }
1005
            else if (options.PositionOnNewList.HasValue)
×
1006
            {
1007
                parameters.Add(CardUpdate.Position(options.PositionOnNewList.Value));
×
1008
            }
1009

1010
            Card card = await GetCardAsync(cardId, new GetCardOptions
5✔
1011
            {
5✔
1012
                CardFields = new CardFields(CardFieldsType.MemberIds, CardFieldsType.LabelIds, CardFieldsType.Labels)
5✔
1013
            }, cancellationToken);
5✔
1014

1015
            switch (options.MemberOptions)
5!
1016
            {
1017
                case MoveCardToBoardOptionsMemberOptions.KeepMembersAlsoOnNewBoardAndRemoveRest:
1018
                    var existingMemberIdsOnNewBoard = (await GetMembersOfBoardAsync(newBoardId, cancellationToken)).Select(x => x.Id);
6✔
1019
                    card.MemberIds = card.MemberIds.Intersect(existingMemberIdsOnNewBoard).ToList();
3✔
1020
                    break;
3✔
1021
                case MoveCardToBoardOptionsMemberOptions.RemoveAllMembersOnCard:
1022
                    card.MemberIds.Clear();
2✔
1023
                    break;
2✔
1024
                default:
1025
                    throw new ArgumentOutOfRangeException();
×
1026
            }
1027

1028
            if (card.LabelIds.Any())
5✔
1029
            {
1030
                card.LabelIds.Clear();
5✔
1031
                switch (options.LabelOptions)
5!
1032
                {
1033
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndCreateMissing:
1034
                    {
1035
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
2✔
1036
                        foreach (Label cardLabel in card.Labels)
28✔
1037
                        {
1038
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
84!
1039
                            if (existingLabel != null)
12!
1040
                            {
1041
                                card.LabelIds.Add(existingLabel.Id);
×
1042
                            }
1043
                            else
1044
                            {
1045
                                //Label need to be added
1046
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
12✔
1047
                                card.LabelIds.Add(newLabel.Id);
12✔
1048
                            }
1049
                        }
1050

1051
                        break;
2✔
1052
                    }
1053
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndColorAndRemoveMissing:
1054
                    {
1055
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
1✔
1056
                        foreach (Label cardLabel in card.Labels)
14✔
1057
                        {
1058
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name && x.Color == cardLabel.Color);
42!
1059
                            if (existingLabel != null)
6!
1060
                            {
1061
                                card.LabelIds.Add(existingLabel.Id);
×
1062
                            }
1063
                        }
1064

1065
                        break;
1066
                    }
1067
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndCreateMissing:
1068
                    {
1069
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
1✔
1070
                        foreach (Label cardLabel in card.Labels)
14✔
1071
                        {
1072
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
42✔
1073
                            if (existingLabel != null)
6!
1074
                            {
1075
                                card.LabelIds.Add(existingLabel.Id);
×
1076
                            }
1077
                            else
1078
                            {
1079
                                //Label need to be added
1080
                                Label newLabel = await AddLabelAsync(new Label(newBoardId, cardLabel.Name, cardLabel.Color), cancellationToken);
6✔
1081
                                card.LabelIds.Add(newLabel.Id);
6✔
1082
                            }
1083
                        }
1084

1085
                        break;
1✔
1086
                    }
1087
                    case MoveCardToBoardOptionsLabelOptions.MigrateToLabelsOfSameNameAndRemoveMissing:
1088
                    {
1089
                        var existingLabels = await GetLabelsOfBoardAsync(newBoardId, cancellationToken);
×
1090
                        foreach (Label cardLabel in card.Labels)
×
1091
                        {
1092
                            Label existingLabel = existingLabels.FirstOrDefault(x => x.Name == cardLabel.Name);
×
1093
                            if (existingLabel != null)
×
1094
                            {
1095
                                card.LabelIds.Add(existingLabel.Id);
×
1096
                            }
1097
                        }
1098

1099
                        break;
1100
                    }
1101
                    case MoveCardToBoardOptionsLabelOptions.RemoveAllLabelsOnCard:
1102
                        //No more Work needed
1103
                        break;
1104
                    default:
1105
                        throw new ArgumentOutOfRangeException();
×
1106
                }
1107
            }
1108

1109
            parameters.Add(CardUpdate.Labels(card.LabelIds.Distinct().ToList()));
5✔
1110
            parameters.Add(CardUpdate.Members(card.MemberIds.Distinct().ToList()));
5✔
1111

1112
            if (options.RemoveDueDate)
5✔
1113
            {
1114
                parameters.Add(CardUpdate.DueDate(null));
4✔
1115
            }
1116

1117
            if (options.RemoveStartDate)
5✔
1118
            {
1119
                parameters.Add(CardUpdate.StartDate(null));
4✔
1120
            }
1121

1122
            return await UpdateCardAsync(cardId, parameters, cancellationToken);
5✔
1123
        }
5✔
1124

1125
        private static string GeneratePayloadForCoverUpdate(CardCover cardCover, List<QueryParameter> parameters)
1126
        {
1127
            //Special code for Cover
1128
            string payload = string.Empty;
5✔
1129
            if (cardCover == null)
5✔
1130
            {
1131
                //Remove cover
1132
                parameters.Add(new QueryParameter("cover", ""));
2✔
1133
            }
1134
            else
1135
            {
1136
                cardCover.PrepareForAddUpdate();
3✔
1137
                if (cardCover.Color != null || cardCover.BackgroundImageId != null)
3!
1138
                {
1139
                    QueryParameter queryParameter = parameters.FirstOrDefault(x => x.Name == "idAttachmentCover");
27✔
1140
                    if (queryParameter != null)
3!
1141
                    {
1142
                        parameters.Remove(queryParameter); //This parameter can't be there while a cover is added
×
1143
                    }
1144
                }
1145

1146
                payload = $"{{\"cover\":{JsonSerializer.Serialize(cardCover)}}}";
3✔
1147
            }
1148

1149
            return payload;
5✔
1150
        }
1151
    }
1152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc