Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Fetch All Subscribers to Newsletter (Paginated) #442

Open
wants to merge 5 commits into
base: dev
Choose a base branch
from
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
@@ -0,0 +1,103 @@
ο»Ώusing AutoMapper;
using Hng.Application.Features.NewsLetterSubscription.Dtos;
using Hng.Application.Features.NewsLetterSubscription.Handlers;
using Hng.Application.Features.NewsLetterSubscription.Queries;
using Hng.Domain.Entities;
using Hng.Infrastructure.Context;
using Microsoft.EntityFrameworkCore;
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;

namespace Hng.Application.Test.NewsLetterSubscription
{
public class GetPaginatedSubscribersHandlerTests
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;
private readonly GetPaginatedSubscribersHandler _handler;

public GetPaginatedSubscribersHandlerTests()
{
// Setup InMemory Database
var options = new DbContextOptionsBuilder<ApplicationDbContext>()
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()) // Unique DB for each test
.Options;

_context = new ApplicationDbContext(options);

// Seed data
_context.NewsLetterSubscribers.AddRange(
new() { Id = Guid.NewGuid(), Email = "[email protected]", LeftOn = null, CreatedAt = DateTime.UtcNow },
new() { Id = Guid.NewGuid(), Email = "[email protected]", LeftOn = null, CreatedAt = DateTime.UtcNow }
);
_context.SaveChanges();

// Mock AutoMapper
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Hng.Domain.Entities.NewsLetterSubscriber, NewsLetterSubscriptionDto>();
});

_mapper = config.CreateMapper();

// Initialize handler
_handler = new GetPaginatedSubscribersHandler(_context, _mapper);
}

[Fact]
public async Task Handle_ShouldReturnActiveSubscribers_WithPagination()
{
// Arrange
var query = new GetPaginatedSubscribersQuery { Page = 1, Limit = 10 };

// Act
var result = await _handler.Handle(query, CancellationToken.None);

// Assert
Assert.True(result.Success);
Assert.Equal(200, result.StatusCode);
Assert.Equal("Subscribers retrieved successfully", result.Message);
Assert.NotNull(result.Data);
Assert.NotEmpty(result.Data);
Assert.Equal(2, result.Pagination.Total); // Total should match seed data
}

[Fact]
public async Task Handle_ShouldReturnNoSubscribers_WhenNoneExist()
{
// Arrange
_context.NewsLetterSubscribers.RemoveRange(_context.NewsLetterSubscribers);
await _context.SaveChangesAsync();

var query = new GetPaginatedSubscribersQuery { Page = 1, Limit = 10 };

// Act
var result = await _handler.Handle(query, CancellationToken.None);

// Assert
Assert.False(result.Success);
Assert.Equal(404, result.StatusCode);
Assert.Equal("No subscribers found", result.Message);
}

[Fact]
public async Task Handle_ShouldReturn400_WhenInvalidPaginationParams()
{
// Arrange
var query = new GetPaginatedSubscribersQuery { Page = -1, Limit = 0 };

// Act
var result = await _handler.Handle(query, CancellationToken.None);

// Assert
Assert.False(result.Success);
Assert.Equal(400, result.StatusCode);
Assert.Equal("Invalid pagination parameters", result.Message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Hng.Application.Features.NewsLetterSubscription.Dtos
{
public class NewsLetterSubscriptionDto
{
public Guid Id { get; set; }
[EmailAddress]
public string Email { get; set; }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
ο»Ώusing System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hng.Application.Features.NewsLetterSubscription.Dtos
{
public class PaginatedSubscribersResponseDto
{
public bool Success { get; set; }
public int StatusCode { get; set; }
public string Message { get; set; }
public IEnumerable<NewsLetterSubscriptionDto> Data { get; set; }
public PaginationMetadata Pagination { get; set; }
}

public class PaginationMetadata
{
public int Page { get; set; }
public int Limit { get; set; }
public int Total { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
ο»Ώusing AutoMapper;
using Hng.Application.Features.NewsLetterSubscription.Dtos;
using Hng.Application.Features.NewsLetterSubscription.Queries;
using Hng.Infrastructure.Context;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hng.Application.Features.NewsLetterSubscription.Handlers
{
public class GetPaginatedSubscribersHandler : IRequestHandler<GetPaginatedSubscribersQuery, PaginatedSubscribersResponseDto>
{
private readonly ApplicationDbContext _context;
private readonly IMapper _mapper;

public GetPaginatedSubscribersHandler(ApplicationDbContext context, IMapper mapper)
{
_context = context;
_mapper = mapper;
}

public async Task<PaginatedSubscribersResponseDto> Handle(GetPaginatedSubscribersQuery request, CancellationToken cancellationToken)
{
if (request.Page <= 0 || request.Limit <= 0)
{
return new PaginatedSubscribersResponseDto
{
Success = false,
StatusCode = 400,
Message = "Invalid pagination parameters"
};
}

var totalSubscribers = await _context.NewsLetterSubscribers.CountAsync(s => s.LeftOn == null, cancellationToken);
var subscribers = await _context.NewsLetterSubscribers
.Where(s => s.LeftOn == null)
.OrderByDescending(s => s.CreatedAt)
.Skip((request.Page - 1) * request.Limit)
.Take(request.Limit)
.ToListAsync(cancellationToken);

if (!subscribers.Any())
{
return new PaginatedSubscribersResponseDto
{
Success = false,
StatusCode = 404,
Message = "No subscribers found"
};
}

return new PaginatedSubscribersResponseDto
{
Success = true,
StatusCode = 200,
Message = "Subscribers retrieved successfully",
Data = _mapper.Map<IEnumerable<NewsLetterSubscriptionDto>>(subscribers), // Keep Guid as Id
Pagination = new PaginationMetadata
{
Page = request.Page,
Limit = request.Limit,
Total = totalSubscribers
}
};
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ο»Ώusing Hng.Application.Features.NewsLetterSubscription.Dtos;
using MediatR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Hng.Application.Features.NewsLetterSubscription.Queries
{
public class GetPaginatedSubscribersQuery : IRequest<PaginatedSubscribersResponseDto>
{
public int Page { get; set; } = 1;
public int Limit { get; set; } = 10;
}
}
2 changes: 2 additions & 0 deletions src/Hng.Domain/Entities/NewsLetterSubscriber.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ public class NewsLetterSubscriber : EntityBase
public string Email { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? LeftOn { get; set; }

public bool IsDeleted { get; set; } = false;

}
}
Loading
Loading