diff --git a/TickAPI/TickAPI.Tests/Customers/Services/CustomerServiceTests.cs b/TickAPI/TickAPI.Tests/Customers/Services/CustomerServiceTests.cs index 9a3eb79..07acc53 100644 --- a/TickAPI/TickAPI.Tests/Customers/Services/CustomerServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Customers/Services/CustomerServiceTests.cs @@ -71,6 +71,34 @@ public async Task CreateNewCustomerAsync_WhenCustomerWithUniqueEmail_ShouldRetur Assert.Equal(createdAt, result.Value!.CreationDate); Assert.Equal(id, result.Value!.Id); } + + [Fact] + public async Task CreateNewCustomerAsync_WhenLastNameIsNull_ShouldReturnNewCustomer() + { + const string email = "new@customer.com"; + const string firstName = "First"; + const string lastName = null; + Guid id = Guid.NewGuid(); + DateTime createdAt = new DateTime(1970, 1, 1, 8, 0, 0, DateTimeKind.Utc); + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock.Setup(m => m.GetCurrentDateTime()).Returns(createdAt); + var customerRepositoryMock = new Mock(); + customerRepositoryMock.Setup(m => m.GetCustomerByEmailAsync(email)).ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, $"customer with email '{email}' not found")); + customerRepositoryMock + .Setup(m => m.AddNewCustomerAsync(It.IsAny())) + .Callback(c => c.Id = id) + .Returns(Task.CompletedTask); + var sut = new CustomerService(customerRepositoryMock.Object, dateTimeServiceMock.Object); + + var result = await sut.CreateNewCustomerAsync(email, firstName, lastName); + + Assert.True(result.IsSuccess); + Assert.Equal(email, result.Value!.Email); + Assert.Equal(firstName, result.Value!.FirstName); + Assert.Equal(lastName, result.Value!.LastName); + Assert.Equal(createdAt, result.Value!.CreationDate); + Assert.Equal(id, result.Value!.Id); + } [Fact] public async Task CreateNewCustomerAsync_WhenCustomerWithNotUniqueEmail_ShouldReturnFailure() diff --git a/TickAPI/TickAPI.Tests/Organizers/Services/OrganizerServiceTests.cs b/TickAPI/TickAPI.Tests/Organizers/Services/OrganizerServiceTests.cs index 769ee7f..1e91aa9 100644 --- a/TickAPI/TickAPI.Tests/Organizers/Services/OrganizerServiceTests.cs +++ b/TickAPI/TickAPI.Tests/Organizers/Services/OrganizerServiceTests.cs @@ -112,6 +112,50 @@ public async Task CreateNewOrganizerAsync_WhenOrganizerDataIsValid_ShouldReturnN Assert.False(result.Value!.IsVerified); Assert.Equal(currentDate, result.Value!.CreationDate); } + + [Fact] + public async Task CreateNewOrganizerAsync_WhenLastNameIsNull_ShouldReturnNewOrganizer() + { + // Arrange + Guid id = Guid.NewGuid(); + const string email = "example@test.com"; + const string firstName = "First"; + const string lastName = null; + const string displayName = "Display"; + DateTime currentDate = new DateTime(1970, 1, 1, 8, 0, 0, DateTimeKind.Utc); + + var organizerRepositoryMock = new Mock(); + organizerRepositoryMock + .Setup(m => m.GetOrganizerByEmailAsync(email)) + .ReturnsAsync(Result.Failure(StatusCodes.Status404NotFound, $"organizer with email '{email}' not found")); + organizerRepositoryMock + .Setup(m => m.AddNewOrganizerAsync(It.IsAny())) + .Callback(o => o.Id = id) + .Returns(Task.CompletedTask); + + var dateTimeServiceMock = new Mock(); + dateTimeServiceMock + .Setup(m => m.GetCurrentDateTime()) + .Returns(currentDate); + + var sut = new OrganizerService( + organizerRepositoryMock.Object, + dateTimeServiceMock.Object + ); + + // Act + var result = await sut.CreateNewOrganizerAsync(email, firstName, lastName, displayName); + + // Assert + Assert.True(result.IsSuccess); + Assert.Equal(id, result.Value!.Id); + Assert.Equal(email, result.Value!.Email); + Assert.Equal(firstName, result.Value!.FirstName); + Assert.Equal(lastName, result.Value!.LastName); + Assert.Equal(displayName, result.Value!.DisplayName); + Assert.False(result.Value!.IsVerified); + Assert.Equal(currentDate, result.Value!.CreationDate); + } [Fact] public async Task CreateNewOrganizerAsync_WhenWithNotUniqueEmail_ShouldReturnFailure() diff --git a/TickAPI/TickAPI/Customers/Models/Customer.cs b/TickAPI/TickAPI/Customers/Models/Customer.cs index 8a78532..5a3c194 100644 --- a/TickAPI/TickAPI/Customers/Models/Customer.cs +++ b/TickAPI/TickAPI/Customers/Models/Customer.cs @@ -7,7 +7,7 @@ public class Customer public Guid Id { get; set; } public string Email { get; set; } public string FirstName { get; set; } - public string LastName { get; set; } + public string? LastName { get; set; } public DateTime CreationDate { get; set; } public ICollection Tickets { get; set; } } \ No newline at end of file diff --git a/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.Designer.cs b/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.Designer.cs new file mode 100644 index 0000000..f5bf758 --- /dev/null +++ b/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.Designer.cs @@ -0,0 +1,357 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using TickAPI.Common.TickApiDbContext; + +#nullable disable + +namespace TickAPI.Migrations +{ + [DbContext(typeof(TickApiDbContext))] + [Migration("20250401224843_NullableLastNames")] + partial class NullableLastNames + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.2") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CategoryEvent", b => + { + b.Property("CategoriesId") + .HasColumnType("uniqueidentifier"); + + b.Property("EventsId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("CategoriesId", "EventsId"); + + b.HasIndex("EventsId"); + + b.ToTable("CategoryEvent"); + }); + + modelBuilder.Entity("TickAPI.Addresses.Models.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FlatNumber") + .HasColumnType("bigint"); + + b.Property("HouseNumber") + .HasColumnType("bigint"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Addresses"); + }); + + modelBuilder.Entity("TickAPI.Admins.Models.Admin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Login") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Admins"); + }); + + modelBuilder.Entity("TickAPI.Categories.Models.Category", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CategoryName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Categories"); + }); + + modelBuilder.Entity("TickAPI.Customers.Models.Customer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AddressId") + .HasColumnType("uniqueidentifier"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EndDate") + .HasColumnType("datetime2"); + + b.Property("EventStatus") + .HasColumnType("int"); + + b.Property("MinimumAge") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizerId") + .HasColumnType("uniqueidentifier"); + + b.Property("StartDate") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("AddressId"); + + b.HasIndex("OrganizerId"); + + b.ToTable("Events"); + }); + + modelBuilder.Entity("TickAPI.Organizers.Models.Organizer", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("CreationDate") + .HasColumnType("datetime2"); + + b.Property("DisplayName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("IsVerified") + .HasColumnType("bit"); + + b.Property("LastName") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Organizers"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("AvailableFrom") + .HasColumnType("datetime2"); + + b.Property("Currency") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("EventId") + .HasColumnType("uniqueidentifier"); + + b.Property("MaxCount") + .HasColumnType("bigint"); + + b.Property("Price") + .HasColumnType("decimal(18,2)"); + + b.HasKey("Id"); + + b.HasIndex("EventId"); + + b.ToTable("TicketTypes"); + }); + + modelBuilder.Entity("TickAPI.Tickets.Models.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("ForResell") + .HasColumnType("bit"); + + b.Property("NameOnTicket") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("OwnerId") + .HasColumnType("uniqueidentifier"); + + b.Property("Seats") + .HasColumnType("nvarchar(max)"); + + b.Property("TypeId") + .HasColumnType("uniqueidentifier"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("TypeId"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("CategoryEvent", b => + { + b.HasOne("TickAPI.Categories.Models.Category", null) + .WithMany() + .HasForeignKey("CategoriesId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.Events.Models.Event", null) + .WithMany() + .HasForeignKey("EventsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.HasOne("TickAPI.Addresses.Models.Address", "Address") + .WithMany() + .HasForeignKey("AddressId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.Organizers.Models.Organizer", "Organizer") + .WithMany("Events") + .HasForeignKey("OrganizerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Address"); + + b.Navigation("Organizer"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.HasOne("TickAPI.Events.Models.Event", "Event") + .WithMany("TicketTypes") + .HasForeignKey("EventId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Event"); + }); + + modelBuilder.Entity("TickAPI.Tickets.Models.Ticket", b => + { + b.HasOne("TickAPI.Customers.Models.Customer", "Owner") + .WithMany("Tickets") + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("TickAPI.TicketTypes.Models.TicketType", "Type") + .WithMany("Tickets") + .HasForeignKey("TypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("Type"); + }); + + modelBuilder.Entity("TickAPI.Customers.Models.Customer", b => + { + b.Navigation("Tickets"); + }); + + modelBuilder.Entity("TickAPI.Events.Models.Event", b => + { + b.Navigation("TicketTypes"); + }); + + modelBuilder.Entity("TickAPI.Organizers.Models.Organizer", b => + { + b.Navigation("Events"); + }); + + modelBuilder.Entity("TickAPI.TicketTypes.Models.TicketType", b => + { + b.Navigation("Tickets"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.cs b/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.cs new file mode 100644 index 0000000..e7741a4 --- /dev/null +++ b/TickAPI/TickAPI/Migrations/20250401224843_NullableLastNames.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace TickAPI.Migrations +{ + /// + public partial class NullableLastNames : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "LastName", + table: "Organizers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + + migrationBuilder.AlterColumn( + name: "LastName", + table: "Customers", + type: "nvarchar(max)", + nullable: true, + oldClrType: typeof(string), + oldType: "nvarchar(max)"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "LastName", + table: "Organizers", + type: "nvarchar(max)", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "LastName", + table: "Customers", + type: "nvarchar(max)", + nullable: false, + defaultValue: "", + oldClrType: typeof(string), + oldType: "nvarchar(max)", + oldNullable: true); + } + } +} diff --git a/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs b/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs index c622432..ca59a48 100644 --- a/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs +++ b/TickAPI/TickAPI/Migrations/TickApiDbContextModelSnapshot.cs @@ -37,6 +37,38 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("CategoryEvent"); }); + modelBuilder.Entity("TickAPI.Addresses.Models.Address", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier"); + + b.Property("City") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Country") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("FlatNumber") + .HasColumnType("bigint"); + + b.Property("HouseNumber") + .HasColumnType("bigint"); + + b.Property("PostalCode") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Street") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("Addresses"); + }); + modelBuilder.Entity("TickAPI.Admins.Models.Admin", b => { b.Property("Id") @@ -89,7 +121,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("nvarchar(max)"); b.Property("LastName") - .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -97,38 +128,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.ToTable("Customers"); }); - modelBuilder.Entity("TickAPI.Events.Models.Address", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("uniqueidentifier"); - - b.Property("City") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Country") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("FlatNumber") - .HasColumnType("bigint"); - - b.Property("HouseNumber") - .HasColumnType("bigint"); - - b.Property("PostalCode") - .IsRequired() - .HasColumnType("nvarchar(max)"); - - b.Property("Street") - .HasColumnType("nvarchar(max)"); - - b.HasKey("Id"); - - b.ToTable("Addresses"); - }); - modelBuilder.Entity("TickAPI.Events.Models.Event", b => { b.Property("Id") @@ -195,7 +194,6 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bit"); b.Property("LastName") - .IsRequired() .HasColumnType("nvarchar(max)"); b.HasKey("Id"); @@ -284,7 +282,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder.Entity("TickAPI.Events.Models.Event", b => { - b.HasOne("TickAPI.Events.Models.Address", "Address") + b.HasOne("TickAPI.Addresses.Models.Address", "Address") .WithMany() .HasForeignKey("AddressId") .OnDelete(DeleteBehavior.Cascade) diff --git a/TickAPI/TickAPI/Organizers/Models/Organizer.cs b/TickAPI/TickAPI/Organizers/Models/Organizer.cs index b7ed81e..f55cc05 100644 --- a/TickAPI/TickAPI/Organizers/Models/Organizer.cs +++ b/TickAPI/TickAPI/Organizers/Models/Organizer.cs @@ -8,7 +8,7 @@ public class Organizer public Guid Id { get; set; } public string Email { get; set; } public string FirstName { get; set; } - public string LastName { get; set; } + public string? LastName { get; set; } public DateTime CreationDate { get; set; } public string DisplayName { get; set; } public bool IsVerified { get; set; }