Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NimblePros.SampleToDo.Core.ProjectAggregate.Events;
using NimblePros.SampleToDo.Core.ContributorAggregate.Events;
using NimblePros.SampleToDo.Core.ProjectAggregate.Events;

namespace NimblePros.SampleToDo.Core.ProjectAggregate;

Expand Down Expand Up @@ -27,6 +28,7 @@ public Project AddItem(ToDoItem newItem)

public Project UpdateName(ProjectName newName)
{
if (Name.Equals(newName)) return this;
Name = newName;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public IncompleteItemsSearchSpec(string searchString)
{
Query
.Where(item => !item.IsDone &&
(item.Title.Contains(searchString) ||
item.Description.Contains(searchString)));
(item.Title.Value.Contains(searchString) ||
item.Description.Value.Contains(searchString)));
}
}
28 changes: 24 additions & 4 deletions sample/src/NimblePros.SampleToDo.Core/ProjectAggregate/ToDoItem.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NimblePros.SampleToDo.Core.ContributorAggregate;
using System.Xml.Linq;
using NimblePros.SampleToDo.Core.ContributorAggregate;
using NimblePros.SampleToDo.Core.ProjectAggregate.Events;

namespace NimblePros.SampleToDo.Core.ProjectAggregate;
Expand All @@ -8,14 +9,19 @@ public class ToDoItem : EntityBase<ToDoItem, ToDoItemId>
public ToDoItem() : this(Priority.Backlog)
{
}
public ToDoItem(ToDoItemTitle title, ToDoItemDescription description) : this(Priority.Backlog)
{
Title = title;
Description = description;
}

public ToDoItem(Priority priority)
{
Priority = priority;
}

public string Title { get; set; } = string.Empty; // TODO: Use Value Object
public string Description { get; set; } = string.Empty; // TODO: Use Value Object
public ToDoItemTitle Title { get; private set; }
public ToDoItemDescription Description { get; private set; }
public ContributorId? ContributorId { get; private set; } // tasks don't have anyone assigned when first created
public bool IsDone { get; private set; }

Expand Down Expand Up @@ -48,9 +54,23 @@ public ToDoItem RemoveContributor()
return this;
}

public ToDoItem UpdateTitle(ToDoItemTitle newTitle)
{
if (Title.Equals(newTitle)) return this;
Title = newTitle;
return this;
}

public ToDoItem UpdateDescription(ToDoItemDescription newDescription)
{
if (Description.Equals(newDescription)) return this;
Description = newDescription;
return this;
}

public override string ToString()
{
string status = IsDone ? "Done!" : "Not done.";
return $"{Id}: Status: {status} - {Title} - Priority: {Priority}";
return $"{Id}: Status: {status} - {Title.Value} - Priority: {Priority}";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Vogen;

namespace NimblePros.SampleToDo.Core.ProjectAggregate;

[ValueObject<string>(conversions: Conversions.SystemTextJson)]
public partial struct ToDoItemDescription
{
public const int MaxLength = 200;
private static Validation Validate(in string description) =>
string.IsNullOrEmpty(description)
? Validation.Invalid("Description cannot be empty")
: description.Length > MaxLength
? Validation.Invalid($"Description cannot be longer than {MaxLength} characters")
: Validation.Ok;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Vogen;

namespace NimblePros.SampleToDo.Core.ProjectAggregate;

[ValueObject<string>(conversions: Conversions.SystemTextJson)]
public partial struct ToDoItemTitle
{
public const int MaxLength = 100;
private static Validation Validate(in string title) =>
string.IsNullOrEmpty(title)
? Validation.Invalid("Title cannot be empty")
: title.Length > MaxLength
? Validation.Invalid($"Title cannot be longer than {MaxLength} characters")
: Validation.Ok;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@ public void Configure(EntityTypeBuilder<ToDoItem> builder)
.HasValueGenerator<VogenIdValueGenerator<AppDbContext, ToDoItem, ToDoItemId>>()
.HasVogenConversion()
.IsRequired();
builder.Property(t => t.Title)
.HasMaxLength(DataSchemaConstants.DEFAULT_NAME_LENGTH)

builder.Property(p => p.Title)
.HasVogenConversion()
.HasMaxLength(ToDoItemTitle.MaxLength)
.IsRequired();

builder.Property(p => p.Description)
.HasVogenConversion()
.HasMaxLength(ToDoItemDescription.MaxLength)
.IsRequired();
builder.Property(t => t.Description)
.HasMaxLength(200);

builder.Property(t => t.ContributorId)
.HasConversion(
v => v.HasValue ? v.Value.Value : (int?)null, // to db
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ namespace NimblePros.SampleToDo.Infrastructure.Data.Config;
[EfCoreConverter<ContributorName>]
[EfCoreConverter<ProjectName>]
[EfCoreConverter<ProjectId>]
[EfCoreConverter<ToDoItemTitle>]
[EfCoreConverter<ToDoItemDescription>]
internal partial class VogenEfCoreConverters;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public async Task<IEnumerable<ToDoItemDto>> ListAsync(int projectId)
var projectParameter = new SqlParameter("@projectId", System.Data.SqlDbType.Int);
var result = await _db.ToDoItems.FromSqlRaw("SELECT Id, Title, Description, IsDone, ContributorId FROM ToDoItems WHERE ProjectId = @ProjectId",
projectParameter) // don't fetch other big columns
.Select(x => new ToDoItemDto(x.Id, x.Title, x.Description, x.IsDone, x.ContributorId))
.Select(x => new ToDoItemDto(x.Id, x.Title.Value, x.Description.Value, x.IsDone, x.ContributorId))
.ToListAsync();

return result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ namespace NimblePros.SampleToDo.UseCases.Projects.AddToDoItem;
/// <param name="Description"></param>
public record AddToDoItemCommand(ProjectId ProjectId,
ContributorId? ContributorId,
string Title,
string Description) : ICommand<Result<ToDoItemId>>;
ToDoItemTitle Title,
ToDoItemDescription Description) : ICommand<Result<ToDoItemId>>;
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,9 @@ public async ValueTask<Result<ToDoItemId>> Handle(AddToDoItemCommand request,
return Result.NotFound();
}

var newItem = new ToDoItem()
{
Title = request.Title!,
Description = request.Description!
};
var newItem = new ToDoItem(title: request.Title!, description: request.Description!);

if(request.ContributorId.HasValue)
if (request.ContributorId.HasValue)
{
newItem.AddContributor(request.ContributorId.Value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public async ValueTask<Result<ProjectWithAllItemsDto>> Handle(GetProjectWithAllI
if (project == null) return Result.NotFound();

var items = project.Items
.Select(i => new ToDoItemDto(i.Id, i.Title, i.Description, i.IsDone, i.ContributorId)).ToList();
.Select(i => new ToDoItemDto(i.Id, i.Title.Value, i.Description.Value, i.IsDone, i.ContributorId)).ToList();
return new ProjectWithAllItemsDto(project.Id, project.Name, items, project.Status.ToString())
;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using NimblePros.SampleToDo.Core.ContributorAggregate;
using NimblePros.SampleToDo.Core.ProjectAggregate;
using NimblePros.SampleToDo.UseCases.Projects.AddToDoItem;
using NimblePros.SampleToDo.Web.Extensions;
using NimblePros.SampleToDo.Web.Projects;

namespace NimblePros.SampleToDo.Web.ProjectEndpoints;
Expand Down Expand Up @@ -30,17 +29,17 @@ public override void Configure()
Title = "Implement user authentication",
Description = "Add JWT-based authentication to the API"
};

// Document possible responses
s.Responses[201] = "Todo item created successfully";
s.Responses[404] = "Project or contributor not found";
s.Responses[400] = "Invalid input data";
s.Responses[500] = "Internal server error";
});

// Add tags for API grouping
Tags("Projects");

// Add additional metadata
Description(builder => builder
.Accepts<CreateToDoItemRequest>("application/json")
Expand All @@ -57,7 +56,7 @@ public override async Task<Results<Created, NotFound, ProblemHttpResult>>
? ContributorId.From(request.ContributorId.Value)
: null;
var command = new AddToDoItemCommand(ProjectId.From(request.ProjectId), contributorId,
request.Title, request.Description);
ToDoItemTitle.From(request.Title), ToDoItemDescription.From(request.Description));
var result = await _mediator.Send(command);

return result.Status switch
Expand Down
42 changes: 20 additions & 22 deletions sample/src/NimblePros.SampleToDo.Web/SeedData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,31 @@ namespace NimblePros.SampleToDo.Web;

public static class SeedData
{
public static readonly Contributor Contributor1 = new (ContributorName.From("Ardalis"));
public static readonly Contributor Contributor2 = new (ContributorName.From("Snowfrog"));
public static readonly Contributor Contributor1 = new(ContributorName.From("Ardalis"));
public static readonly Contributor Contributor2 = new(ContributorName.From("Snowfrog"));
public static readonly Project TestProject1 = new Project(ProjectName.From("Test Project"));
public static readonly ToDoItem ToDoItem1 = new ToDoItem
{
Title = "Get Sample Working",
Description = "Try to get the sample to build."
};
public static readonly ToDoItem ToDoItem2 = new ToDoItem
{
Title = "Review Solution",
Description = "Review the different projects in the solution and how they relate to one another."
};
public static readonly ToDoItem ToDoItem3 = new ToDoItem
{
Title = "Run and Review Tests",
Description = "Make sure all the tests run and review what they are doing."
};

public static readonly ToDoItem ToDoItem1 =
new ToDoItem(title: ToDoItemTitle.From("Get Sample Working"),
description: ToDoItemDescription.From("Try to get the sample to build."));

public static readonly ToDoItem ToDoItem2 =
new ToDoItem(title: ToDoItemTitle.From("Review Solution"),
description: ToDoItemDescription.From("Review the different projects in the solution and how they relate to one another."));


public static readonly ToDoItem ToDoItem3 =
new ToDoItem(title: ToDoItemTitle.From("Run and Review Tests"),
description: ToDoItemDescription.From("Make sure all the tests run and review what they are doing."));

public static async Task InitializeAsync(AppDbContext dbContext)
{
if (await dbContext.ToDoItems.AnyAsync())
{
return; // DB has been seeded
}
if (await dbContext.ToDoItems.AnyAsync())
{
return; // DB has been seeded
}

await PopulateTestDataAsync(dbContext);
await PopulateTestDataAsync(dbContext);
}

public static async Task PopulateTestDataAsync(AppDbContext dbContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public async Task AddsProjectAndSetsId()
var project = new Project(testProjectName);

var item = new ToDoItem();
item.Title = "test item title";
item.UpdateTitle(ToDoItemTitle.From("test item title"));
project.AddItem(item);

await repository.AddAsync(project);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,8 @@ public class Project_AddItem
[Fact]
public void AddsItemToItems()
{
var _testItem = new ToDoItem
{
Title = "title",
Description = "description"
};
var _testItem = new ToDoItem(title: ToDoItemTitle.From("title"),
description: ToDoItemDescription.From("description"));

_testProject.AddItem(_testItem);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,56 +1,51 @@
using NimblePros.SampleToDo.Core.Interfaces;
using NimblePros.SampleToDo.Core.ProjectAggregate;
using NimblePros.SampleToDo.Core.Services;
using NimblePros.SharedKernel;

namespace NimblePros.SampleToDo.UnitTests.Core.Services;

public class ToDoItemSearchServiceTests
{
private readonly IToDoItemSearchService _service;
private readonly IRepository<Project> _repo = Substitute.For<IRepository<Project>>();

public ToDoItemSearchServiceTests()
{
_service = new ToDoItemSearchService(_repo);

}

[Fact]
public async Task ReturnsValidationErrors()
{
var projects = await _service.GetAllIncompleteItemsAsync(ProjectId.From(0), string.Empty);

Assert.NotEmpty(projects.ValidationErrors);
}

[Fact]
public async Task ReturnsProjectNotFound()
{
var projects = await _service.GetAllIncompleteItemsAsync(ProjectId.From(100), "Hello");

Assert.Equal(ResultStatus.NotFound, projects.Status);
}

[Fact]
public async Task ReturnsAllIncompleteItems()
{
var title = "Some Title";
Project project = new Project(ProjectName.From("Cool Project"));

project.AddItem(new ToDoItem
{
Title = title,
Description = "Some Description"
});

project.AddItem(new ToDoItem(title: ToDoItemTitle.From(title), description: ToDoItemDescription.From("Some Description")));

_repo.FirstOrDefaultAsync(Arg.Any<ISpecification<Project>>(), Arg.Any<CancellationToken>())
.Returns(project);

var projects = await _service.GetAllIncompleteItemsAsync(ProjectId.From(1), title);

Assert.Empty(projects.ValidationErrors);
Assert.Equal(projects.Value.First().Title, title);
Assert.Equal(projects.Value.First().Title.Value, title);
Assert.Equal(project.Items.Count(), projects.Value.Count);
}
}
10 changes: 5 additions & 5 deletions sample/tests/NimblePros.SampleToDo.UnitTests/ToDoItemBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@ public ToDoItemBuilder Id(int id)
return this;
}

public ToDoItemBuilder Title(string title)
public ToDoItemBuilder Title(String title)
{
_todo.Title = title;
_todo.UpdateTitle(ToDoItemTitle.From(title));
return this;
}

public ToDoItemBuilder Description(string description)
public ToDoItemBuilder Description(String description)
{
_todo.Description = description;
_todo.UpdateDescription(ToDoItemDescription.From(description));
return this;
}

public ToDoItemBuilder WithDefaultValues()
{
_todo = new ToDoItem() { Id = ToDoItemId.From(1), Title = "Test Item", Description = "Test Description" };
_todo = new ToDoItem(title: ToDoItemTitle.From("Test Item"), description: ToDoItemDescription.From("Test Description")) { Id = ToDoItemId.From(1) };

return this;
}
Expand Down
Loading