Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
be2f07c
added files for shopping cart service
kubapoke Apr 26, 2025
109be60
added necessary endpoint mocks for the shopping carts controller
kubapoke Apr 27, 2025
d138180
added mocks for the rest of necessary endpoints
kubapoke Apr 27, 2025
56e3a12
Merge branch 'develop' into feat/shopping-cart
kubapoke Apr 27, 2025
7ef6a91
Merge branch 'develop' into feat/shopping-cart
kubapoke May 2, 2025
cc84275
Merge branch 'develop' into feat/shopping-cart
kubapoke May 2, 2025
64e5670
added the base of ShoppingCartService.cs methods (headers still requi…
kubapoke May 2, 2025
e6619a4
added acquiring cart contents (from service) and adding ticket to car…
kubapoke May 3, 2025
b209d46
removed unnecessary using directive
kubapoke May 9, 2025
6c59aab
Merge branch 'develop' into feat/shopping-cart
kubapoke May 14, 2025
7f5f88a
added getting tickets from cart
kubapoke May 14, 2025
91fb526
removed NameOnTicket being necessary
kubapoke May 14, 2025
c3222df
implemented the basic version of getting tickets
kubapoke May 15, 2025
95698fe
reworked ticket buying logic to make the pipeline from frontend more …
kubapoke May 16, 2025
3edf2d6
relegated some of the ticket adding logic to the repository
kubapoke May 16, 2025
84dd97f
started updating some ticket retrieval logic
kubapoke May 16, 2025
9781f8d
Merge branch 'develop' into feat/shopping-cart
kubapoke May 20, 2025
ab228be
small aesthetic change in TicketRepository.cs
kubapoke May 20, 2025
0f0e843
updated cart retrieval logic to return more data
kubapoke May 20, 2025
5e3b95f
added functionality for removal of tickets from the shopping cart
kubapoke May 20, 2025
e877eae
implemented getting cart due amount and part of the checkout process
kubapoke May 21, 2025
68d9662
added payment endpoint
kubapoke May 21, 2025
05da8e5
changed due amount calculations
kubapoke May 21, 2025
2338c8e
implemented adding created tickets to database
kubapoke May 22, 2025
b0703de
tickets removed from cart after purchase
kubapoke May 22, 2025
fe4cf05
added methods for manipulating ticket type counters
kubapoke May 22, 2025
c1097b3
added reserved ticket counting
kubapoke May 22, 2025
ec35bc9
fixed counting amount of reserved tickets
kubapoke May 22, 2025
98ddd74
added redis service method for retrieving keys
kubapoke May 23, 2025
bc6d27a
added background service for syncing cart counters with their actual …
kubapoke May 23, 2025
a9bc327
fixed tests to account for changed method logic
kubapoke May 23, 2025
1bccf0c
changed ToObjectResult in Result.cs to return 200 on success instead …
kubapoke May 24, 2025
b649e81
ShoppingCartsController.cs uses ToObjectResult
kubapoke May 24, 2025
f88d487
stylistic fixes
kubapoke May 24, 2025
e1efea5
added returning currency when getting cart items
kubapoke May 24, 2025
e8e3acc
program reads shopping cart lifetime and syncing interval from appset…
kubapoke May 24, 2025
5eb6440
updated tests to account for ToObjectResult method change
kubapoke May 24, 2025
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
29 changes: 27 additions & 2 deletions TickAPI/TickAPI.Tests/Common/Results/ResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ public void Failure_ShouldReturnResultWithError()
Assert.Equal(statusCode, result.StatusCode);
}

[Fact]
public void PropagateError_WhenResultWithErrorPassed_ShouldReturnResultWithError()
{
const int statusCode = 500;
const string errorMsg = "error message";
var resultWithError = Result.Failure(statusCode, errorMsg);

var result = Result.PropagateError(resultWithError);

Assert.True(result.IsError);
Assert.False(result.IsSuccess);
Assert.Equal(errorMsg, result.ErrorMsg);
Assert.Equal(statusCode, result.StatusCode);
}

[Fact]
public void PropagateError_WhenGenericResultWithErrorPassed_ShouldReturnResultWithError()
{
Expand All @@ -55,6 +70,16 @@ public void PropagateError_WhenGenericResultWithErrorPassed_ShouldReturnResultWi
Assert.Equal(statusCode, result.StatusCode);
}

[Fact]
public void PropagateError_WhenResultWithSuccessPassed_ShouldThrowArgumentException()
{
var resultWithSuccess = Result.Success();

var act = () => Result.PropagateError(resultWithSuccess);

Assert.Throws<ArgumentException>(act);
}

[Fact]
public void PropagateError_WhenGenericResultWithSuccessPassed_ShouldThrowArgumentException()
{
Expand Down Expand Up @@ -97,7 +122,7 @@ public void ToObjectResult_WhenResultIsSuccess_ShouldReturnObjectResultWithDefau
// Assert
Assert.IsType<ObjectResult>(objectResult);
Assert.Equal(StatusCodes.Status200OK, objectResult.StatusCode);
Assert.Null(objectResult.Value);
Assert.Equal(string.Empty, objectResult.Value);
}

[Fact]
Expand All @@ -113,6 +138,6 @@ public void ToObjectResult_WhenResultIsSuccessWithCustomStatusCode_ShouldReturnO
// Assert
Assert.IsType<ObjectResult>(objectResult);
Assert.Equal(customSuccessCode, objectResult.StatusCode);
Assert.Null(objectResult.Value);
Assert.Equal(string.Empty, objectResult.Value);
}
}
4 changes: 2 additions & 2 deletions TickAPI/TickAPI.Tests/Events/Services/EventServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,8 @@ public async Task GetEventDetailsAsync_WhenSuccessful_ShouldReturnEventDetails()
.ReturnsAsync(Result<Event>.Success(@event));

ticketServiceMock
.Setup(m => m.GetNumberOfAvailableTicketsByType(It.IsAny<TicketType>()))
.Returns((TicketType input) =>
.Setup(m => m.GetNumberOfAvailableTicketsByTypeAsync(It.IsAny<TicketType>()))
.ReturnsAsync((TicketType input) =>
Result<uint>.Success((uint)(input.Price / 10))
);

Expand Down
92 changes: 70 additions & 22 deletions TickAPI/TickAPI.Tests/Tickets/Services/TicketServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,76 @@
using TickAPI.Customers.Models;
using TickAPI.Events.Models;
using TickAPI.Organizers.Models;
using TickAPI.ShoppingCarts.Abstractions;
using TickAPI.Tickets.Abstractions;
using TickAPI.Tickets.DTOs.Response;
using TickAPI.Tickets.Models;
using TickAPI.Tickets.Services;
using TickAPI.TicketTypes.Abstractions;
using TickAPI.TicketTypes.Models;

namespace TickAPI.Tests.Tickets.Services;

public class TicketServiceTests
{
[Fact]
public void GetNumberOfAvailableTicketsByType_AmountsAreCorrect_ShouldReturnCorrectNumberOfTickets()
public async Task GetNumberOfAvailableTicketsByType_AmountsAreCorrect_ShouldReturnCorrectNumberOfTickets()
{
// Arrange
var type = new TicketType { MaxCount = 30 };
var ticketList = new List<Ticket>(new Ticket[10]);

var ticketRepositoryMock = new Mock<ITicketRepository>();
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

ticketRepositoryMock
.Setup(m => m.GetAllTicketsByTicketType(type))
.Returns(ticketList.AsQueryable());

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
shoppingCartRepositoryMock
.Setup(s => s.GetAmountOfTicketTypeAsync(type.Id))
.ReturnsAsync(Result<long>.Success(0));

var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = sut.GetNumberOfAvailableTicketsByType(type);
var result = await sut.GetNumberOfAvailableTicketsByTypeAsync(type);

// Assert
Assert.True(result.IsSuccess);
Assert.Equal(20u, result.Value);
}

[Fact]
public void GetNumberOfAvailableTicketsByType_WhenMoreTicketExistThanMaxCount_ShouldReturnError()
public async Task GetNumberOfAvailableTicketsByType_WhenMoreTicketExistThanMaxCount_ShouldReturnError()
{
// Arrange
var type = new TicketType { MaxCount = 30 };
var ticketList = new List<Ticket>(new Ticket[50]);

var ticketRepositoryMock = new Mock<ITicketRepository>();
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

ticketRepositoryMock
.Setup(m => m.GetAllTicketsByTicketType(type))
.Returns(ticketList.AsQueryable());

shoppingCartRepositoryMock
.Setup(s => s.GetAmountOfTicketTypeAsync(type.Id))
.ReturnsAsync(Result<long>.Success(0));

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = sut.GetNumberOfAvailableTicketsByType(type);
var result = await sut.GetNumberOfAvailableTicketsByTypeAsync(type);

// Assert
Assert.True(result.IsError);
Expand Down Expand Up @@ -111,6 +127,9 @@ public async Task GetTicketsForResellAsync_WhenDataIsValid_ShouldReturnSuccess()
ticketRepositoryMock.Setup(repo => repo.GetTicketsByEventId(eventId))
.Returns(allTickets);

var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginatedTickets = new PaginatedData<Ticket>(
new List<Ticket> { ticket1, ticket2 },
page,
Expand Down Expand Up @@ -156,7 +175,8 @@ public async Task GetTicketsForResellAsync_WhenDataIsValid_ShouldReturnSuccess()

var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand Down Expand Up @@ -206,6 +226,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForResell_ShouldReturnEm
ticketRepositoryMock.Setup(repo => repo.GetTicketsByEventId(eventId))
.Returns(tickets);

var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginatedData = new PaginatedData<Ticket>(
new List<Ticket>(),
page,
Expand Down Expand Up @@ -234,7 +257,8 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForResell_ShouldReturnEm
.Returns(mappedData);
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand Down Expand Up @@ -272,14 +296,18 @@ public async Task GetTicketsForResellAsync_WhenPaginationFails_ShouldPropagateEr
var ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(repo => repo.GetTicketsByEventId(eventId))
.Returns(tickets);

var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginationServiceMock = new Mock<IPaginationService>();
paginationServiceMock.Setup(p => p.PaginateAsync(It.IsAny<IQueryable<Ticket>>(), pageSize, page))
.ReturnsAsync(Result<PaginatedData<Ticket>>.Failure(statusCode, errorMsg));

var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand All @@ -304,6 +332,9 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForEvent_ShouldReturnEmp
ticketRepositoryMock.Setup(repo => repo.GetTicketsByEventId(eventId))
.Returns(tickets);

var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginatedData = new PaginatedData<Ticket>(
new List<Ticket>(),
page,
Expand Down Expand Up @@ -332,7 +363,8 @@ public async Task GetTicketsForResellAsync_WhenNoTicketsForEvent_ShouldReturnEmp
.Returns(mappedData);
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForResellAsync(eventId, page, pageSize);
Expand Down Expand Up @@ -380,9 +412,11 @@ public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnT
}
},
};
string email = "123@123.com";
string scanurl = "http://localhost";
Mock<ITicketRepository> ticketRepositoryMock = new Mock<ITicketRepository>();
const string email = "123@123.com";
const string scanurl = "http://localhost";
var ticketRepositoryMock = new Mock<ITicketRepository>();
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginationServiceMock = new Mock<IPaginationService>();

Expand All @@ -392,7 +426,8 @@ public async Task GetTicketDetailsAsync_WhenTicketExistsForTheUser_ShouldReturnT
var qrServiceMock = new Mock<IQRCodeService>();
qrServiceMock.Setup(m => m.GenerateQrCode(scanurl)).Returns([]);

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act

Expand Down Expand Up @@ -426,18 +461,21 @@ public async Task GetTicketDetailsAsync_WhenTicketDoesNotExistForTheUser_ShouldR

// Arrange

Guid ticketId = Guid.NewGuid();
string email = "123@123.com";
string scanUrl = "http://localhost";
var ticketId = Guid.NewGuid();
const string email = "123@123.com";
const string scanUrl = "http://localhost";

Mock<ITicketRepository> ticketRepositoryMock = new Mock<ITicketRepository>();
var ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(m => m.GetTicketWithDetailsByIdAndEmailAsync(ticketId, email)).
ReturnsAsync(Result<Ticket>.Failure(StatusCodes.Status404NotFound, "Ticket with this id doesn't exist " +
"for this user"));
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act

Expand Down Expand Up @@ -513,6 +551,8 @@ public async Task GetTicketsForCustomerAsync_WithValidInput_ReturnsSuccessResult

var ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(r => r.GetTicketsByCustomerEmail(email)).Returns(tickets.AsQueryable());
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginationServiceMock = new Mock<IPaginationService>();
paginationServiceMock.Setup(p => p.PaginateAsync(tickets.AsQueryable(), pageSize, page))
Expand All @@ -523,7 +563,8 @@ public async Task GetTicketsForCustomerAsync_WithValidInput_ReturnsSuccessResult

var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize);
Expand Down Expand Up @@ -555,6 +596,9 @@ public async Task GetTicketsForCustomerAsync_WhenUserHasNoTickets_ReturnsEmptyPa

var ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(r => r.GetTicketsByCustomerEmail(email)).Returns(emptyTickets.AsQueryable());

var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();

var paginationServiceMock = new Mock<IPaginationService>();
paginationServiceMock.Setup(p => p.PaginateAsync(emptyTickets.AsQueryable(), pageSize, page)).ReturnsAsync(paginatedResult);
Expand All @@ -563,7 +607,8 @@ public async Task GetTicketsForCustomerAsync_WhenUserHasNoTickets_ReturnsEmptyPa

var qrServiceMock = new Mock<IQRCodeService>();

var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var result = await sut.GetTicketsForCustomerAsync(email, page, pageSize);
Expand All @@ -580,9 +625,12 @@ public async Task ScanTicket_WhenScanningSuccesful_ShouldReturnSuccess()
var guid = Guid.NewGuid();
var ticketRepositoryMock = new Mock<ITicketRepository>();
ticketRepositoryMock.Setup(m => m.MarkTicketAsUsed(guid)).ReturnsAsync(Result.Success());
var ticketTypeRepositoryMock = new Mock<ITicketTypeRepository>();
var shoppingCartRepositoryMock = new Mock<IShoppingCartRepository>();
var paginationServiceMock = new Mock<IPaginationService>();
var qrServiceMock = new Mock<IQRCodeService>();
var sut = new TicketService(ticketRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);
var sut = new TicketService(ticketRepositoryMock.Object, ticketTypeRepositoryMock.Object,
shoppingCartRepositoryMock.Object, paginationServiceMock.Object, qrServiceMock.Object);

// Act
var res = await sut.ScanTicket(guid);
Expand Down
1 change: 1 addition & 0 deletions TickAPI/TickAPI/Common/Redis/Abstractions/IRedisService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public interface IRedisService
public Task<long> DecrementValueAsync(string key, long value = 1);
public Task<long?> GetLongValueAsync(string key);
public Task<bool> SetLongValueAsync(string key, long value, TimeSpan? expiry = null);
public Task<IEnumerable<string>> GetKeysByPatternAsync(string pattern);
}
8 changes: 8 additions & 0 deletions TickAPI/TickAPI/Common/Redis/Services/RedisService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ namespace TickAPI.Common.Redis.Services;

public class RedisService : IRedisService
{
private readonly IConnectionMultiplexer _connectionMultiplexer;
private readonly IDatabase _database;
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

public RedisService(IConnectionMultiplexer connectionMultiplexer)
{
_connectionMultiplexer = connectionMultiplexer;
_database = connectionMultiplexer.GetDatabase();
}

Expand Down Expand Up @@ -85,6 +87,12 @@ public async Task<bool> SetLongValueAsync(string key, long value, TimeSpan? expi
return await RetryAsync(async () => await _database.StringSetAsync(key, value.ToString(), expiry));
}

public async Task<IEnumerable<string>> GetKeysByPatternAsync(string pattern)
{
var server = _connectionMultiplexer.GetServer(_connectionMultiplexer.GetEndPoints().First());
return server.Keys(pattern: pattern).Select(k => k.ToString());
}

private static async Task<T> RetryAsync<T>(Func<Task<T>> action, int retryCount = 3, int millisecondsDelay = 100)
{
var attempt = 0;
Expand Down
12 changes: 11 additions & 1 deletion TickAPI/TickAPI/Common/Results/Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ public static Result Failure(int statusCode, string errorMsg)
return new Result(false, statusCode, errorMsg);
}

public static Result PropagateError(Result other)
{
if (other.IsSuccess)
{
throw new ArgumentException("Trying to propagate error from successful value");
}

return Failure(other.StatusCode, other.ErrorMsg);
}

public static Result PropagateError<TE>(Result<TE> other)
{
if (other.IsSuccess)
Expand All @@ -47,7 +57,7 @@ public ObjectResult ToObjectResult(int successCode = StatusCodes.Status200OK)
};
}

return new ObjectResult(null)
return new ObjectResult(string.Empty)
{
StatusCode = successCode
};
Expand Down
Loading
Loading