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
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/backend/Livraria/.vs
/backend/Livraria/obj
/backend/.vs
/backend/src/LivrariaSolution/.vs
/backend/src/LivrariaSolution/Livraria.Application/obj
/backend/src/LivrariaSolution/Livraria.Infrastructure/obj
/backend/src/LivrariaSolution/Livraria/obj
/backend/src/LivrariaSolution/LivrariaDomain/obj
/backend/src/LivrariaSolution/Livraria.Application/bin
/backend/src/LivrariaSolution/Livraria.Infrastructure/bin
/backend/src/LivrariaSolution/Livraria/bin
/backend/src/LivrariaSolution/LivrariaDomain/bin
backend/src/LivrariaSolution/Livraria/logs/app-log.txt
/frontend/.vscode
/backend/src/LivrariaSolution/Livraria.Application.Tests/obj
/backend/src/LivrariaSolution/Livraria.Domain.Tests/obj
/backend/src/LivrariaSolution/Livraria.Application.Tests/bin
/backend/src/LivrariaSolution/Livraria.Domain.Tests/bin
Binary file added Currículo_Carlos 1.pdf
Binary file not shown.
88 changes: 88 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,91 @@ Para ajudar a livraria foi solicitado a você desenvolver uma aplicação web pa
2. Realize o teste;
3. Adicione seu currículo na raiz do repositório;
4. Envie-nos o PULL-REQUEST para que seja avaliado;


# Projeto Livraria Full-Stack (ASP.NET Core + Angular)

> Aplicação web para gerenciamento de um inventário de livraria, desenvolvida como um projeto full-stack. O backend foi construído com ASP.NET Core 8 e o frontend com Angular, implementando um sistema de autenticação JWT com controle de acesso baseado em funções.

## Tecnologias Utilizadas

### Backend
* **.NET 8**
* **ASP.NET Core Web API**
* **Entity Framework Core**
* **SQL Server**
* **Autenticação JWT (JSON Web Token)**
* **Swagger/OpenAPI**

### Frontend
* **Angular 17+**
* **TypeScript**
* **Angular Material** (Para componentes de UI)
* **Angular Reactive Forms**
* **Angular Router**

## Pré-requisitos

Antes de começar, garanta que você tem as seguintes ferramentas instaladas na sua máquina:
* [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
* [SQL Server 2014](https://www.microsoft.com/pt-br/sql-server/sql-server-downloads) ou superior
* [Visual Studio 2022](https://visualstudio.microsoft.com/pt-br/vs/) ou VS Code com C# Dev Kit.
* [Node.js](https://nodejs.org/en/) (Versão LTS 18.x ou 20.x recomendada)
* [Angular CLI](https://angular.io/cli) instalado globalmente (`npm install -g @angular/cli`)

## Como Executar o Projeto

O projeto está dividido em duas pastas: `backend` e `frontend`. Siga os passos abaixo em ordem.

### 1. Backend (.NET Core API)

A API é o cérebro da aplicação e precisa estar rodando para que o frontend funcione.

1. **Configurar o Banco de Dados:**
* Abra o arquivo `appsettings.json` na pasta do backend.
* Localize a `ConnectionString` e a ajuste para apontar para a sua instância local do SQL Server.
* Abra um terminal na pasta do backend e execute as migrations para criar o banco de dados e as tabelas:
```sh
dotnet ef database update
```

2. **Executar a API:**
* Abra o arquivo de solução (`.sln`) no Visual Studio 2022.
* Pressione **F5** ou clique no botão de "Iniciar" (com o perfil HTTP) para executar a aplicação.
* Uma janela do navegador abrirá com a interface do Swagger, geralmente em uma URL que será necessário usar, pois você precisará dela para o frontend.

### 2. Frontend (Angular)

Com a API rodando, agora podemos iniciar a interface do usuário.

1. **Configurar a URL da API:**
* Navegue até a pasta do frontend.
* Abra os arquivos de serviço: `src/app/services/auth.service.ts` e `src/app/services/book.service.ts`.
* Verifique se a variável `private apiUrl` em ambos os arquivos corresponde **exatamente** à URL base em que sua API está rodando.

2. **Instalar Dependências:**
* Abra um **novo terminal** na pasta do frontend.
* Execute o comando abaixo para instalar todos os pacotes necessários:
```sh
npm install
```

3. **Executar a Aplicação:**
* Após a instalação, execute o comando para iniciar o servidor de desenvolvimento:
```sh
ng serve -o
```
* A aplicação será compilada e aberta automaticamente no seu navegador, geralmente em `http://localhost:4200`.

## Credenciais de Teste

A aplicação possui dois níveis de acesso. Utilize a tela de registro para registrar um usuário administrador e outro público.

## Funcionalidades Implementadas

* **Autenticação de Usuários:** Sistema completo de Login e Registro.
* **Controle de Acesso por Função:** A interface se adapta dinamicamente, mostrando/escondendo funcionalidades com base no cargo do usuário (Admin vs. Público).
* **Proteção de Rotas:** Páginas internas são protegidas com `AuthGuard`, redirecionando usuários não autorizados para a tela de login.
* **Gerenciamento de Token:** O token JWT é enviado automaticamente em todas as requisições para endpoints protegidos via `HTTP Interceptor`.
* **CRUD de Livros:** Administradores podem realizar todas as operações de Criar, Ler, Atualizar e Excluir livros.
* **Interface Responsiva:** Utilização do Angular Material para criar uma UI/UX moderna, consistente e adaptável a diferentes tamanhos de tela.
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
using FluentAssertions;
using Livraria.Application.DTOs;
using Livraria.Application.Services;
using Livraria.Domain.Entities;
using Livraria.Domain.Interfaces;
using Microsoft.Extensions.Logging;
using Moq;

namespace Livraria.Application.Tests
{
public class BookServiceTests
{
private readonly Mock<IBookRepository> _bookRepositoryMock;
private readonly Mock<ILogger<BookService>> _loggerMock;
private readonly BookService _bookService;

public BookServiceTests()
{
_bookRepositoryMock = new Mock<IBookRepository>();
_loggerMock = new Mock<ILogger<BookService>>();
_bookService = new BookService(_bookRepositoryMock.Object, _loggerMock.Object);
}

#region CreateBookAsync Tests

[Fact]
public async Task CreateBookAsync_WithUniqueBook_ShouldCreateAndReturnBookId()
{
// Arrange
var createDto = new CreateBookDTO() { Title = "O Alquimista", Author = "Paulo Coelho", PublicationYear = 1988 };

_bookRepositoryMock
.Setup(r => r.GetByTitleAsync(createDto.Title))
.ReturnsAsync((Book)null);

// Act
var resultId = await _bookService.CreateBookAsync(createDto);

// Assert
resultId.Should().NotBeEmpty();

_bookRepositoryMock.Verify(r => r.AddAsync(It.IsAny<Book>()), Times.Once);
_bookRepositoryMock.Verify(r => r.SaveChangesAsync(), Times.Once);
}

[Fact]
public async Task CreateBookAsync_WhenBookAlreadyExists_ShouldThrowInvalidOperationException()
{
// Arrange
var createDto = new CreateBookDTO() { Title = "Dom Casmurro", Author = "Machado de Assis", PublicationYear = 1899 };

var existingBook = Book.Create(createDto.Title, createDto.Author, createDto.PublicationYear);
_bookRepositoryMock
.Setup(r => r.GetByTitleAsync(createDto.Title))
.ReturnsAsync(existingBook);

// Act
Func<Task> act = () => _bookService.CreateBookAsync(createDto);

// Assert
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Um livro inserido j� existe na base de dados.");

_bookRepositoryMock.Verify(r => r.AddAsync(It.IsAny<Book>()), Times.Never);
_bookRepositoryMock.Verify(r => r.SaveChangesAsync(), Times.Never);
}

#endregion

#region UpdateBookAsync Tests

[Fact]
public async Task UpdateBookAsync_WhenBookExists_ShouldUpdateBook()
{
// Arrange
var bookId = Guid.NewGuid();
var updateDto = new UpdateBookDTO() { Title = "Novo T�tulo", Author = "Novo Autor", PublicationYear = 2025 };
var existingBook = Book.Create("T�tulo Antigo", "Autor Antigo", 2000);

_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync(existingBook);

// Act
await _bookService.UpdateBookAsync(bookId, updateDto);

// Assert
_bookRepositoryMock.Verify(r => r.Update(It.IsAny<Book>()), Times.Once);
_bookRepositoryMock.Verify(r => r.SaveChangesAsync(), Times.Once);

existingBook.Title.Should().Be(updateDto.Title);
existingBook.Author.Should().Be(updateDto.Author);
}

[Fact]
public async Task UpdateBookAsync_WhenBookNotFound_ShouldThrowInvalidOperationException()
{
// Arrange
var bookId = Guid.NewGuid();
var updateDto = new UpdateBookDTO() { Title = "Novo T�tulo", Author = "Novo Autor", PublicationYear = 2025 };

_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync((Book)null);

// Act
Func<Task> act = () => _bookService.UpdateBookAsync(bookId, updateDto);

// Assert
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Livro n�o encontrado.");
}

#endregion

#region DeleteBookAsync Tests

[Fact]
public async Task DeleteBookAsync_WhenBookExists_ShouldDeleteBook()
{
// Arrange
var bookId = Guid.NewGuid();
var existingBook = Book.Create("Livro a ser Deletado", "Autor", 2010);

_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync(existingBook);

// Act
await _bookService.DeleteBookAsync(bookId);

// Assert
_bookRepositoryMock.Verify(r => r.Delete(existingBook), Times.Once);
_bookRepositoryMock.Verify(r => r.SaveChangesAsync(), Times.Once);
}

[Fact]
public async Task DeleteBookAsync_WhenBookNotFound_ShouldThrowInvalidOperationException()
{
// Arrange
var bookId = Guid.NewGuid();
_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync((Book)null);

// Act
Func<Task> act = () => _bookService.DeleteBookAsync(bookId);

// Assert
await act.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Livro n�o encontrado.");
}

#endregion

#region GetBookByIdAsync Tests

[Fact]
public async Task GetBookByIdAsync_WhenBookExists_ShouldReturnBookDTO()
{
// Arrange
var bookId = Guid.NewGuid();
var book = Book.Create("O Hobbit", "J.R.R. Tolkien", 1937);

_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync(book);

// Act
var result = await _bookService.GetBookByIdAsync(bookId);

// Assert
result.Should().NotBeNull();
result.Should().BeOfType<BookDTO>();
result?.Title.Should().Be(book.Title);
}

[Fact]
public async Task GetBookByIdAsync_WhenBookNotFound_ShouldReturnNull()
{
// Arrange
var bookId = Guid.NewGuid();
_bookRepositoryMock.Setup(r => r.GetByIdAsync(bookId)).ReturnsAsync((Book)null);

// Act
var result = await _bookService.GetBookByIdAsync(bookId);

// Assert
result.Should().BeNull();
}

#endregion

#region GetAllBooksAsync Tests

[Fact]
public async Task GetAllBooksAsync_WhenBooksExist_ShouldReturnListOfBookDTOs()
{
// Arrange
var books = new List<Book>
{
Book.Create("Livro 1", "Autor A", 2001),
Book.Create("Livro 2", "Autor B", 2002)
};
_bookRepositoryMock.Setup(r => r.GetAllSortedByNameAsync()).ReturnsAsync(books);

// Act
var result = await _bookService.GetAllBooksAsync();

// Assert
result.Should().NotBeNull();
result.Should().HaveCount(2);
result.First().Title.Should().Be("Livro 1");
}

[Fact]
public async Task GetAllBooksAsync_WhenNoBooksExist_ShouldReturnEmptyList()
{
// Arrange
_bookRepositoryMock.Setup(r => r.GetAllSortedByNameAsync()).ReturnsAsync(new List<Book>());

// Act
var result = await _bookService.GetAllBooksAsync();

// Assert
result.Should().NotBeNull();
result.Should().BeEmpty();
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="FluentAssertions" Version="8.6.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Livraria.Application\Livraria.Application.csproj" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

</Project>
10 changes: 10 additions & 0 deletions backend/src/LivrariaSolution/Livraria.Application/DTOs/BookDTO.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Livraria.Application.DTOs
{
public class BookDTO
{
public Guid Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public int PublicationYear { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Livraria.Application.DTOs
{
public class CreateBookDTO
{
public string Title { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public int PublicationYear { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Livraria.Application.DTOs
{
public class UpdateBookDTO
{
public string Title { get; set; } = string.Empty;
public string Author { get; set; } = string.Empty;
public int PublicationYear { get; set; }
}
}
Loading