diff --git a/.gitignore b/.gitignore
index 7d999646db..5c08714f72 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,139 @@
__pycache__/
*.log
-*.swp
\ No newline at end of file
+*.swp
+
+backend/node_modules
+frontend/node_modules
+
+package-lock.json
+
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+.pnpm-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Snowpack dependency directory (https://snowpack.dev/)
+web_modules/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional stylelint cache
+.stylelintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variable files
+.env.development.local
+.env.test.local
+.env.production.local
+.env.local
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+.parcel-cache
+
+# Next.js build output
+.next
+out
+
+# Nuxt.js build / generate output
+.nuxt
+dist
+
+# Gatsby files
+.cache/
+# Comment in the public line in if your project uses Gatsby and not Next.js
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# public
+
+# vuepress build output
+.vuepress/dist
+
+# vuepress v2.x temp and cache directory
+.temp
+.cache
+
+# Docusaurus cache and generated files
+.docusaurus
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# TernJS port file
+.tern-port
+
+# Stores VSCode versions used for testing VSCode extensions
+.vscode-test
+
+# yarn v2
+.yarn/cache
+.yarn/unplugged
+.yarn/build-state.yml
+.yarn/install-state.gz
+.pnp.*
diff --git a/README.md b/README.md
index a722f737ae..a5f7a747e0 100644
--- a/README.md
+++ b/README.md
@@ -1,65 +1,72 @@
-# Bem-vindo ao ESS Base Project! 🗂 🛠
+# Bem-vindo ao Projeto de ESS
-Este projeto serve como ponto de partida para o desenvolvimento de aplicações utilizando os *frameworks* de *backend* e *frontend* escolhidos pela equipe.
+Este projeto foi desenvolvido para a disciplina de ESS, no período 2023.2.
----
+## Grupo
-## Sobre o Projeto
+Ana Sofia Lima (asosl),
+Guilherme Maranhão (gmsmr),
+João Victor Omena (jvrco),
+João Vitor Mergulhão (jvlm2),
+Luís Guilherme Nunes (lgmmn),
+Maria Letícia Nascimento (mlmn3),
+Pedro Vítor Monte (pvom),
+Rebeca Menezes (ram3).
-O Projeto Base de ESS utiliza o conceito de ***[Subtrees]('https://www.atlassian.com/br/git/tutorials/git-subtree')*** do Git para integrar dois repositórios separados, um para o *backend* e outro para o *frontend*. Esses repositórios são clonados dentro deste projeto, em pastas separadas, permitindo o desenvolvimento simultâneo das camadas de *frontend* e *backend*.
+# Framework
+MERN: NodeJS, React, Express e MongoDB.
-## Passo a Passo 🚶
+## Rodando a primeira vez
-### Crie um *Fork*
+Baixe NodeJS, React e MongoDB.
-Se você ainda não tem, faça um *fork* **[deste repositório](https://github.com/Software-Engineering-Assistantship/ess-base-project)** para a sua conta do GitHub.
+### No *backend*:
-### Clone o seu *Fork*
-
-Clone o repositório forkado para o seu ambiente de desenvolvimento local.
-### Instale o *Python 3* 🐍
+```sh
+npm i bcrypt cors dotenv express mongoose multer nodemailer nodemon jsonwebtoken cookie-parser
-Certifique-se de ter o *Python* instalado em seu sistema. Se necessário, faça o download e a instalação do *Python* em https://www.python.org/.
+```
-### Abra o seu projeto localmente 💻
+No *.env* coloque a porta 3001 e um nome para o banco de dados.
-Abra o terminal e navegue até o diretório do seu projeto base.
+Após a configuração inicial,
-### Escolha os *Frameworks* 📝
+```sh
+npm run dev
-Para prosseguir com a criação do projeto base, é importante que você e sua equipe tenham decidido previamente quais *frameworks* de *backend* e *frontend* serão utilizados.
+```
-#### *Frameworks* Suportados:
+No terminal, deverá aparecer:
-- ***Frontend*:** React ⚛️, Angular 🅰️, Vue.js 🔥 e Next.js 🇳
-- ***Backend*:** NodeJS 🚀 e FastAPI ⚡️
+Server started on port 3001
+Connected to data base
-Certifique-se de que todos estejam alinhados na escolha dos *frameworks* antes de prosseguir com o processo de criação do projeto. Isso garantirá que você esteja utilizando as tecnologias desejadas e poderá aproveitar ao máximo o potencial oferecido por cada *framework* selecionado.
+#### Para rodar os testes:
-### Crie o Projeto Base 📁
+Pela primeira vez:
-Para criar o projeto, execute o comando abaixo:
```sh
-pip install inquirer && python3 ./config/cli.py
+npm i axios ts-jest jest-cucumber
```
-ou, caso o comando ```python3``` não exista em sua máquina, execute:
+Para rodar:
+
```sh
-pip install inquirer && python ./config/cli.py
-```
+npm run test
-Esse comando instalará a biblioteca [inquirer](https://python-inquirer.readthedocs.io/en/latest/) e executará o arquivo cli.py localizado na pasta config. A partir desse momento, você terá acesso a um processo interativo que irá guiá-lo durante a configuração do projeto.
+```
+### No *frontend*:
-### Comece a desenvolver! 🚀
+```sh
+npm i react-scripts
-Comece a desenvolver sua aplicação utilizando esse projeto base como ponto de partida!
-Após a conclusão do processo de criação, o projeto já estará estruturado com os diretórios de *backend* e *frontend* separados. Cada um desses projetos é baseado nos *frameworks* que você escolheu, e eles contêm um arquivo README com instruções detalhadas sobre como configurar e executar cada um deles. Portanto, é altamente recomendado que você leia os respectivos READMEs para obter as informações necessárias. Não deixe essa etapa de lado, pois os READMEs fornecerão orientações valiosas para começar a trabalhar nos projetos de *backend* e *frontend* com facilidade.
+```
-*Que a força esteja com vocês! 🪐💪✨*
+Após a configuração inicial,
----
-## Contribuindo 🤝
+```sh
+npm start
-Se você tiver sugestões de melhorias ou encontrar problemas no projeto base, sinta-se à vontade para abrir uma issue neste repositório. Sua contribuição é valiosa para aprimorarmos continuamente o projeto.
+```
diff --git a/backend/.env b/backend/.env
new file mode 100644
index 0000000000..7378177520
--- /dev/null
+++ b/backend/.env
@@ -0,0 +1,2 @@
+PORT=3001
+DBNAME=test2
\ No newline at end of file
diff --git a/backend/config/middleware.js b/backend/config/middleware.js
new file mode 100644
index 0000000000..8829e42823
--- /dev/null
+++ b/backend/config/middleware.js
@@ -0,0 +1,36 @@
+const jwt = require('jsonwebtoken');
+
+// Função para gerar um token JWT
+const generateToken = (user) => {
+ return jwt.sign({ userId: user._id }, 'secreto', { expiresIn: '1h' });
+};
+
+// Middleware para verificar o token em cada requisição
+const verifyToken = (req, res, next) => {
+ const token = req.headers.authorization;
+ if (!token) {
+ return res.status(401).json({ error: 'Token não fornecido' });
+ }
+
+ jwt.verify(token, 'secreto', (err, decodedToken) => {
+ if (err) {
+ return res.status(403).json({ error: 'Token inválido' });
+ }
+ req.userId = decodedToken.userId;
+ next();
+ });
+};
+
+// Rota de login
+const user_login = async (req, res) => {
+ // Verificação de credenciais aqui
+
+ // Se o usuário for autenticado com sucesso
+ const token = generateToken(user);
+ return res.json({ token });
+};
+
+module.exports = {
+ user_login,
+ verifyToken
+};
\ No newline at end of file
diff --git a/backend/config/multer.js b/backend/config/multer.js
new file mode 100644
index 0000000000..170343b931
--- /dev/null
+++ b/backend/config/multer.js
@@ -0,0 +1,15 @@
+const multer = require("multer")
+const path = require("path")
+
+const storage = multer.diskStorage({
+ destination: function(req, file, cb){
+ cb(null, "uploads/")
+ },
+ filename: function(req, file, cb){
+ cb(null, Date.now() + path.extname(file.originalname))
+ }
+})
+
+const upload = multer({ storage })
+
+module.exports = upload
\ No newline at end of file
diff --git a/backend/controllers/feedController.js b/backend/controllers/feedController.js
new file mode 100644
index 0000000000..d721a2539a
--- /dev/null
+++ b/backend/controllers/feedController.js
@@ -0,0 +1,47 @@
+const Restaurant = require("../models/Restaurant")
+const Review = require("../models/Review")
+
+// selects 5 random restaurants registered
+const get_random_restaurants = async (req, res) => {
+ const restaurants = await Restaurant.find()
+
+ if (restaurants.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há restaurantes cadastrados' })
+ }
+ else {
+ const random_restaurants = restaurants.sort(() => Math.random() - 0.5).slice(0, 5);
+ res.json(random_restaurants)
+ }
+}
+
+// selects 5 random reviews registered
+const get_random_reviews = async (req, res) => {
+ const reviews = await Review.find()
+
+ if (reviews.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há reviews cadastradas' })
+ }
+ else {
+ const random_reviews = reviews.sort(() => Math.random() - 0.5).slice(0, 5);
+ res.json(random_reviews)
+ }
+}
+
+// finds the 5 most liked reviews
+const get_most_liked_reviews = async (req, res) => {
+ const reviews = await Review.find()
+
+ if (reviews.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há reviews cadastradas' })
+ }
+ else {
+ const most_liked_reviews = await Review.find().sort({ likes: -1 }).limit(5);
+ res.json(most_liked_reviews)
+ }
+}
+
+module.exports = {
+ get_random_restaurants,
+ get_random_reviews,
+ get_most_liked_reviews
+}
diff --git a/backend/controllers/followersController.js b/backend/controllers/followersController.js
new file mode 100644
index 0000000000..79e50a8a73
--- /dev/null
+++ b/backend/controllers/followersController.js
@@ -0,0 +1,234 @@
+const User = require("../models/User")
+const sendEmail = require("../utils/sendEmail")
+const getUser = require("./userController")
+
+//list of followers
+const user_followers_get = async (req, res) => {
+
+ //gets the user ID from parameters
+ const user_page = await User.findById(req.params.id)
+
+ //if there's no user with the ID
+ if(!user_page){
+ return res.status(404).json({ error: 'Usuário não encontrado'})
+
+ //if there is a user with the ID from the parameters
+ } else{
+
+ //if there is no followers, return status:404 and an empty JSON
+ if (!user_page.followers.length){
+ return res.status(404).json(null)
+
+ //if there is at least one follower, return status:200 and the list
+ } else {
+
+ let list_followers = []
+
+ for (const follower_id of user_page.followers){
+ let user = await User.findById(follower_id)
+
+ list_followers.push(user)
+
+ }
+
+ return res.status(200).json(list_followers)
+ }
+ }
+
+}
+
+//list of users a given user follows
+const user_following_get = async (req, res) => {
+
+ //gets the user ID from parameters (page of the user)
+ const user_page = await User.findById(req.params.id)
+
+ //no user with the ID from the parameters
+ if(!user_page){
+ return res.status(404).json({ error: 'Usuário não encontrado'})
+
+ } else{
+
+ //if the user follows no one: return status:404 and an empty JSON
+ if (!user_page.following.length){
+ return res.status(404).json(null)
+
+ //if there's at least one user followed, return a sucess status and the list
+ } else {
+ let list_following = []
+
+ for (const followed_id of user_page.following){
+ let user = await User.findById(followed_id)
+
+ list_following.push(user)
+
+ }
+
+ return res.status(200).json(list_following)
+ }
+ }
+
+}
+
+//user logged in (body) follows another user (parameter)
+const user_follow = async (req, res) => {
+
+ //user that will be followed
+ //ID in the parameters
+ const user_page = await User.findById(req.params.idp)
+
+ if(!user_page){
+ return res.status(404).json({ error: 'Usuário não encontrado'})
+
+ } else{
+ //user following
+ //ID in body
+ const user_log = await User.findById(req.params.idl)
+
+ try{
+
+ //if user_page is not followed by user_log
+ if (!user_page.followers.includes(user_log.id)){
+
+ //update user_page's followers list
+ //push user_log's id to the list
+ let user_followed = await User.findByIdAndUpdate(
+ {_id: user_page._id},
+ {$push : {followers: user_log.id}},
+ {new: true}
+ )
+
+ //update user_log's following list
+ //push user_page's id to the list
+ let user_following = await User.findByIdAndUpdate(
+ {_id: user_log._id},
+ {$push: {following: user_page.id}},
+ {new: true}
+ )
+
+ //send email to the followed user
+ //notification about a new follower and the follower's page link
+ try{
+ const send_to = user_followed.email
+ const subject = "Você tem um novo seguidor!"
+ const message = `
+
Olá, ${user_followed.name}
+
Você tem um novo seguidor: ${user_following.name}
+ Página de ${user_following.name}
+ `
+
+ //from ../utils/sendEmail
+ const status_email = await sendEmail(subject, message, send_to)
+
+ //return status:200 and JSON with followed.id + followers and follower.id + following
+ return res.status(200).json({
+ followed: {"id": user_followed.id,
+ "followers": user_followed.followers},
+
+ follower: {"id": user_following.id,
+ "following": user_following.following},
+
+ status_email: status_email})
+
+ } catch (error_email) {
+
+
+ return res.status(200).json({
+ followed: {"id": user_followed.id,
+ "followers": user_followed.followers},
+
+ follower: {"id": user_following.id,
+ "following": user_following.following},
+
+ status_email:"error"})
+ }
+
+ //if user_log already follows user_page
+ } else {
+ //return status:409 conflict and JSON with followed.id + followers and follower.id + following
+ return res.status(409).json({ error: "usuário já segue",
+
+ status_email: "error"})
+
+ }
+
+ } catch (e) {
+
+ return res.status(500).error({ error: "Erro ao seguir"})
+ }
+ }
+
+}
+
+//user logged in (body) unfollows another user (parameter)
+const user_unfollow = async (req, res) => {
+
+ //user that will be unfollowed
+ //ID in the parameters
+ const user_page = await User.findById(req.params.idp)
+
+ if(!user_page){
+ return res.status(404).json({ error: 'Usuário não encontrado'})
+
+ } else{
+
+ //user unfollowing
+ //ID in body
+ const user_log = await User.findById(req.params.idl)
+
+ try{
+
+ //if user_page is followed by user_log
+ if (user_page.followers.includes(user_log.id)){
+
+ //update user_page's followers list
+ //pull user_log's id from the list
+ let user_unfollowed = await User.findByIdAndUpdate(
+ {_id: user_page._id},
+ {$pull : {followers: user_log.id}},
+ {new: true}
+ )
+
+ //update user_log's following list
+ //pull user_page's id from the list
+ let user_unfollowing = await User.findByIdAndUpdate(
+ {_id: user_log._id},
+ {$pull: {following: user_page.id}},
+ {new: true}
+ )
+
+ //return status:200 and JSON with unfollowed.id + follower and unfollowing.id + following
+ return res.status(200).json({
+ unfollowed: {"id": user_unfollowed.id,
+ "followers": user_unfollowed.followers},
+
+ unfollower: {"id": user_unfollowing.id,
+ "following": user_unfollowing.following}})
+
+ //if user_log does not follow user_page
+ } else {
+
+ //return status:409 conflict and JSON with unfollowed.id + follower and unfollowing.id + following
+ return res.status(409).json({
+ unfollowed: {"id": user_page.id,
+ "followers": user_page.followers},
+
+ unfollower: {"id": user_log.id,
+ "following": user_log.following}})
+ }
+
+ } catch (e) {
+
+ return res.status(500).send({ error : "Erro ao deixar de seguir"})
+ }
+ }
+}
+
+
+module.exports = {
+
+ user_followers_get,
+ user_following_get,
+ user_follow,
+ user_unfollow
+}
diff --git a/backend/controllers/forumController.js b/backend/controllers/forumController.js
new file mode 100644
index 0000000000..67014c65e6
--- /dev/null
+++ b/backend/controllers/forumController.js
@@ -0,0 +1,191 @@
+const Forum = require("../models/Forum")
+
+const forumPosts_get = async (req, res) => {
+ const forumPosts = await Forum.find()
+
+ if (forumPosts.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há posts no fórum.' })
+ }else{
+ res.json(forumPosts)
+ }
+}
+
+const forumComments_get = async (req, res) => {
+ const forumComments = await Forum.find()
+
+ if (forumComments.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há comentários no post.' })
+ }else{
+ res.json(forumComments)
+ }
+}
+
+const singleForumPost_get = async (req, res) => {
+ const forumPost = await Forum.findById(req.params.id)
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Post não encontrado.' })
+ }
+ else{
+ res.json(forumPost)
+ }
+}
+
+const forumPost_create = async (req, res) => {
+ const expectedProperties = ['title', 'content', 'author'];
+
+ // checa se todas as propriedades obrigatórias estão presentes
+ const areAllPropertiesPresent = expectedProperties.every(prop => req.body.hasOwnProperty(prop));
+
+ if (!areAllPropertiesPresent) {
+ return res.status(400).json({ error: 'Dados obrigatórios estão incompletos na solicitação.' });
+ }
+
+ const imagePath = req.file ? req.file.path : null;
+ const {title, content, author} = req.body
+
+ const forumPost = await Forum.create({
+ title,
+ content,
+ author,
+ attachedImg: imagePath
+ })
+
+ res.json(forumPost)
+}
+
+const forumComment_create = async (req, res) => {
+ const expectedProperties = ['content', 'author'];
+
+ // checa se todas as propriedades obrigatórias estão presentes
+ const areAllPropertiesPresent = expectedProperties.every(prop => req.body.hasOwnProperty(prop));
+
+ if (!areAllPropertiesPresent) {
+ return res.status(400).json({ error: 'Dados obrigatórios estão incompletos na solicitação.' });
+ }
+
+ const { content, author } = req.body;
+
+ try {
+ const forumPost = await Forum.findById(req.params.postId);
+
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Post não encontrado.' });
+ }
+
+ // Adiciona um novo comentário ao array de comentários no post do fórum
+ forumPost.comments.push({
+ content,
+ author
+ });
+
+ // Salva as alterações no post do fórum
+ await forumPost.save();
+
+ res.json(forumPost);
+ } catch (error) {
+ console.error(error);
+ res.status(500).json({ error: 'Erro interno do servidor.' });
+ }
+};
+
+
+const forumPost_edit = async (req, res) => {
+ let forumPost = await Forum.findById(req.params.id, req.body)
+
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Post não encontrado.' })
+ }
+
+ forumPost.set(req.body)
+ res.json(forumPost)
+}
+
+const forumComment_edit = async (req, res) => {
+ const expectedProperties = ['content'];
+
+ // Checa se todas as propriedades obrigatórias estão presentes
+ const areAllPropertiesPresent = expectedProperties.every(prop => req.body.hasOwnProperty(prop));
+
+ if (!areAllPropertiesPresent) {
+ return res.status(400).json({ error: 'Dados obrigatórios estão incompletos na solicitação.' });
+ }
+
+ const { content } = req.body;
+
+ try {
+ const forumPost = await Forum.findById(req.params.postId);
+
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Post não encontrado.' });
+ }
+
+ const commentIndex = forumPost.comments.findIndex(comment => comment._id.toString() === req.params.commentId);
+
+ if (commentIndex === -1) {
+ return res.status(404).json({ error: 'Comentário não encontrado.' });
+ }
+
+ // Atualiza o conteúdo do comentário
+ forumPost.comments[commentIndex].content = content;
+
+ // Salva as alterações no post do fórum
+ await forumPost.save();
+
+ res.json(forumPost);
+ } catch (error) {
+ console.error(error);
+ res.status(500).json({ error: 'Erro interno do servidor.' });
+ }
+};
+
+
+
+const forumPost_delete = async (req, res) => {
+ const forumPost = await Forum.findByIdAndDelete(req.params.id)
+
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Restaurante não encontrado' })
+ }else{
+ res.json(forumPost)
+ }
+}
+
+const forumComment_delete = async (req, res) => {
+ try {
+ const forumPost = await Forum.findById(req.params.postId);
+
+ if (!forumPost) {
+ return res.status(404).json({ error: 'Post não encontrado.' });
+ }
+
+ const commentIndex = forumPost.comments.findIndex(comment => comment._id.toString() === req.params.commentId);
+
+ if (commentIndex === -1) {
+ return res.status(404).json({ error: 'Comentário não encontrado.' });
+ }
+
+ // Remove o comentário do array de comentários
+ forumPost.comments.splice(commentIndex, 1);
+
+ // Salva as alterações no post do fórum
+ await forumPost.save();
+
+ res.json(forumPost);
+ } catch (error) {
+ console.error(error);
+ res.status(500).json({ error: 'Erro interno do servidor.' });
+ }
+};
+
+
+module.exports = {
+ forumPosts_get,
+ forumComments_get,
+ singleForumPost_get,
+ forumPost_create,
+ forumComment_create,
+ forumPost_edit,
+ forumComment_edit,
+ forumPost_delete,
+ forumComment_delete
+}
\ No newline at end of file
diff --git a/backend/controllers/listController.js b/backend/controllers/listController.js
new file mode 100644
index 0000000000..cbae734782
--- /dev/null
+++ b/backend/controllers/listController.js
@@ -0,0 +1,124 @@
+const ListModel = require('../models/List')
+const bcrypt = require('bcrypt');
+const User = require('../models/User')
+const Restaurant = require('../models/Restaurant')
+const mongoose = require('mongoose');
+
+const list_create = async (req, res) =>{
+ const user = await User.findById(req.params.id)
+
+ // check if user exists
+ if(!user){
+ console.log("Usuário não encontrado!")
+ return
+ }
+
+ if(!req.body.name){
+ return res.status(404).json({ error: 'Nome não definido' })
+ }
+
+ // create new list with the proper parameters
+ const list = new ListModel({
+ name: req.body.name,
+ restaurants: req.body.restaurants,
+ description: req.body.description,
+ author: user.name
+ })
+
+ // save the list
+ await list.save()
+
+ // making an array of the restaurant names to show it on json
+ const restaurantIds = req.body.restaurants;
+ const restaurants = await Restaurant.find({ _id: { $in: restaurantIds } }, 'name');
+
+ res.json({
+ name: list.name,
+ description: list.description,
+ restaurants: restaurants,
+ author: list.author,
+ id: list.id,
+ __v: list.__v
+ })
+}
+
+
+const list_get = async (req,res) => {
+ const list = await ListModel.findById(req.params.id)
+ if(!list){
+ return res.status(404).json({ error: 'Lista não encontrada' })
+ }
+ res.json(list)
+}
+
+
+const list_get_all = async (req,res) => {
+ const lists = await ListModel.find()
+
+ if(lists.length === 0){
+ return res.status(404).json({ error: 'Você ainda não cadastrou nenhuma lista' })
+ }
+
+ res.json(lists)
+}
+
+const list_edit = async(req, res) => {
+ let list = await ListModel.findById(req.params.id);
+
+ if(!list){
+ return res.status(404).json({ error: 'Lista não encontrada' })
+ }
+ else{
+ // if the list was found
+
+ const remove_List = req.body.remove_list
+ const add_List = req.body.add_list
+
+ // removing what was required
+ if (remove_List && remove_List.length > 0) {
+ remove_List.forEach(removeId =>{
+ if(list.restaurants.includes(removeId)){
+ list.restaurants.pop(removeId)
+ }
+ })
+ }
+
+ // adding what was required
+ if(add_List && add_List.length > 0){
+ add_List.forEach(addId => {
+ if(!list.restaurants.includes(addId)){
+ list.restaurants.push(addId)
+ }
+ });
+ }
+
+ // updating infos
+ list = await ListModel.findByIdAndUpdate(
+ req.params.id,
+ { name: req.body.name, description: req.body.description, restaurants: list.restaurants},
+ { new: true }
+ );
+ }
+
+ // return data
+ return res.json({list})
+}
+
+const list_delete = async (req, res) => {
+ const list = await ListModel.findByIdAndDelete(req.params.id)
+
+ if (!list){
+ return res.status(404).json({error: 'Lista não encontrada'})
+ } else{
+ res.json(list)
+ }
+}
+
+
+module.exports = {
+ list_create,
+ list_edit,
+ list_get,
+ list_get_all,
+ list_delete
+}
diff --git a/backend/controllers/ratingController.js b/backend/controllers/ratingController.js
new file mode 100644
index 0000000000..6e54aad019
--- /dev/null
+++ b/backend/controllers/ratingController.js
@@ -0,0 +1,82 @@
+const Rating = require("../models/Rating")
+const Review = require("../models/Review")
+
+//give a rating
+const rating_post = async (req, res) => {
+
+ const rating = Rating.create(req.body)
+
+ res.json(rating)
+}
+
+const rating_edit = async (req, res) => {
+ req.body = JSON.parse(JSON.stringify(req.body))
+
+ let rating = await Rating.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+ let review = await Review.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+
+ if (!rating) {
+ return res.status(404).json({ error: 'Nota não encontrada' })
+ }
+
+ if (review) {
+ review.set('rating', req.body.rating)
+ review.save()
+ }
+
+ rating.set('rating', req.body.rating)
+ rating.save()
+
+
+ res.json(rating)
+}
+
+//avg of ratings
+const rating_avg = async (req, res) => {
+
+ const averages = await Rating.aggregate([{$group: {_id: "$restaurant", avg_rating: {$avg: "$rating"}}}]);
+
+
+ //testar o parse aqui
+
+ for(avg of averages) {
+ if (avg._id == req.params.idrest) {
+ const average = avg.avg_rating
+ res.json(average)
+ break;
+ }
+ }
+}
+
+const rating_get = async (req, res) => {
+
+ const rating = await Rating.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+
+ if (!rating) {
+ return res.status(404).json({ error: 'Nota não encontrada' })
+ }
+ else{
+ res.json(rating)
+ }
+}
+
+const rating_list = async (req, res) => {
+
+ const rating = await Rating.find({restaurant: req.params.idrest})
+
+ if (!rating) {
+ return res.status(404).json({ error: 'Não existem notas cadastradas para este restaurante' })
+ }
+ else{
+ res.json(rating)
+ }
+}
+
+module.exports = {
+ rating_post,
+ rating_avg,
+ rating_get,
+ rating_list,
+ rating_edit
+}
+
diff --git a/backend/controllers/restaurantController.js b/backend/controllers/restaurantController.js
new file mode 100644
index 0000000000..c125557d51
--- /dev/null
+++ b/backend/controllers/restaurantController.js
@@ -0,0 +1,140 @@
+const Restaurant = require("../models/Restaurant")
+
+const restaurants_get = async (req, res) => {
+ const restaurants = await Restaurant.find()
+
+ if (restaurants.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há restaurantes cadastrados' })
+ }else{
+ res.json(restaurants)
+ }
+}
+
+const restaurant_profile_get = async (req, res) => {
+ const restaurant = await Restaurant.findById(req.params.id)
+ if (!restaurant) {
+ return res.status(404).json({ error: 'Restaurante não encontrado' })
+ }
+ else{
+ res.json(restaurant)
+ }
+}
+
+const restaurant_create = async (req, res) => {
+
+ const expectedProperties = ['name', 'address', 'typeOfFood'];
+
+ req.body = JSON.parse(JSON.stringify(req.body))
+
+ console.log(req.body)
+
+ // checa se todas as propriedades obrigatórias estão presentes
+ const areAllPropertiesPresent = expectedProperties.every(prop => Object.prototype.hasOwnProperty.call(req.body, prop));
+
+ if (!areAllPropertiesPresent) {
+ return res.status(400).json({ error: 'Dados obrigatórios estão incompletos na solicitação' });
+ }
+
+ const {street, number, city, neighborhood} = req.body.address
+
+ // checa se já existe um restaurante com mesmo nome e mesmo endereço
+ const restaurantExist = await Restaurant.findOne({name : req.body.name, 'address.street': street, 'address.number': number, 'address.city': city, 'address.neighborhood': neighborhood})
+
+ if (restaurantExist) {
+ return res.status(400).json({ error: 'Restaurante já cadastrado' });
+ }
+
+ let profileImage = 'Noneundefined'
+ let coverImage = 'Noneundefined'
+
+ if(req.files.file1 !== undefined){
+ const file1 = req.files.file1[0];
+ profileImage = file1.destination + file1.filename
+ }
+
+ if(req.files.file2 !== undefined){
+ const file2 = req.files.file2[0];
+ coverImage = file2.destination + file2.filename
+ }
+
+ const {name, address, typeOfFood, site} = req.body
+
+ const restaurant = await Restaurant.create({
+ name,
+ address,
+ typeOfFood,
+ site,
+ profileImage: profileImage,
+ coverImage: coverImage
+ })
+
+ res.json(restaurant)
+}
+
+const restaurant_edit = async (req, res) => {
+ try {
+ let restaurant = await Restaurant.findById(req.params.id);
+
+ req.body = JSON.parse(JSON.stringify(req.body))
+
+ console.log(req.body)
+
+ if (!restaurant) {
+ return res.status(404).json({ error: 'Restaurante não encontrado' });
+ }
+
+ const {name, address, typeOfFood, site} = req.body
+
+ // Verificar se os novos dados já existem em outro restaurante
+ const restaurantExist = await Restaurant.findOne({ 'name': name, 'address': address });
+
+ if (restaurantExist && restaurantExist._id.toString() !== req.params.id) {
+ return res.status(400).json({ error: 'Os dados de endereço e nome do restaurante não podem ser iguais a outro já cadastrado' });
+ }
+
+ const restaurantOld = await Restaurant.findById(req.params.id)
+
+ if(req.files.file1 !== undefined){
+ const file1 = req.files.file1[0];
+ req.body.profileImage = file1.destination + file1.filename
+ } else {
+ req.body.profileImage = restaurantOld.profileImage
+ }
+
+ if(req.files.file2 !== undefined){
+ const file2 = req.files.file2[0];
+ req.body.coverImage = file2.destination + file2.filename
+ } else {
+ req.body.coverImage = restaurantOld.coverImage
+ }
+
+ restaurant.set(req.body);
+
+ // salva os dados
+ await restaurant.save();
+
+ res.json(restaurant);
+ } catch (error) {
+
+ console.error(error);
+ res.status(500).json({ error: 'Erro ao salvar as mudanças no restaurante' });
+ }
+}
+
+const restaurant_delete = async (req, res) => {
+ const restaurant = await Restaurant.findByIdAndDelete(req.params.id)
+
+ if (!restaurant) {
+ return res.status(404).json({ error: 'Restaurante não encontrado' })
+ }else{
+ res.json(restaurant)
+ }
+}
+
+module.exports = {
+ restaurants_get,
+ restaurant_profile_get,
+ restaurant_create,
+ restaurant_edit,
+ restaurant_delete,
+}
diff --git a/backend/controllers/reviewController.js b/backend/controllers/reviewController.js
new file mode 100644
index 0000000000..6c9b5baf01
--- /dev/null
+++ b/backend/controllers/reviewController.js
@@ -0,0 +1,104 @@
+const Review = require("../models/Review")
+const Rating = require("../models/Rating")
+
+//show all registered reviews from a given restaurant
+const review_show = async (req, res) => {
+ const reviews = await Review.find({restaurant: req.params.idrest})
+
+ if (reviews.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há reviews para este restaurante' })
+ } else {
+ res.json(reviews)
+ }
+}
+
+//show the page of a given review
+const review_get = async (req, res) => {
+ const review = await Review.findOne({user: req.params.iduser, restaurant: req.params.idrest});
+
+
+ if (!review) {
+ return res.status(404).json({ error: 'Review não encontrado' })
+ }
+ else{
+ res.json(review)
+ }
+
+}
+
+//create a new review
+const review_post = async (req, res) => {
+ let existReview = await Review.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+
+ if (existReview) {
+ return res.status(400).json({ error: 'Review já existente' })
+ }
+
+ let rating = await Rating.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+
+ const review = await Review.create(req.body)
+
+ if (rating) {
+ review.set('rating', rating.rating);
+ } else {
+ const newRating = await Rating.create({user: req.body.user, restaurant: req.body.restaurant, rating: req.body.rating})
+ }
+ res.json(review)
+}
+
+//Edit a review with the given id
+const review_edit = async (req, res) => {
+ req.body = JSON.parse(JSON.stringify(req.body))
+
+ let review = await Review.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+ let rating = await Rating.findOne({user: req.params.iduser, restaurant: req.params.idrest})
+
+ if (!review) {
+ return res.status(404).json({ error: 'Review não encontrado' })
+ }
+
+ rating.set('rating', req.body.rating)
+
+ review.set(req.body);
+
+ rating.save()
+
+ review.save()
+
+ res.json(review)
+}
+
+// delete a review with the given id
+const review_delete = async (req, res) => {
+
+ let review = await Review.findOneAndDelete({user: req.params.iduser, restaurant: req.params.idrest})
+ let rating = await Rating.findOneAndDelete({user: req.params.iduser, restaurant: req.params.idrest})
+
+
+ if (!review) {
+ return res.status(404).json({ error: 'Review não encontrado' })
+ }
+
+ res.json(review)
+
+}
+
+// show the review from a given user
+const review_user = async (req, res) => {
+ const reviews = await Review.find({restaurant: req.params.iduser})
+
+ if (reviews.length === 0) {
+ return res.status(404).json({ error: 'Ainda não há reviews para este usuário' })
+ } else {
+ res.json(reviews)
+ }
+}
+
+module.exports = {
+ review_show,
+ review_get,
+ review_post,
+ review_edit,
+ review_delete,
+ review_user
+}
diff --git a/backend/controllers/searchesController.js b/backend/controllers/searchesController.js
new file mode 100644
index 0000000000..eb7a0ba41f
--- /dev/null
+++ b/backend/controllers/searchesController.js
@@ -0,0 +1,38 @@
+const Restaurant = require("../models/Restaurant")
+
+// does the search of the system
+const search_get = async (req, res) => {
+ const restaurants_ = await Restaurant.find()
+
+ if (restaurants_.length === 0) {
+ return res.status(404).json({ error: 'Nenhum restaurante foi encontrado porque ainda não há restaurantes cadastrados' })
+ }
+
+ const expected_properties = ['name'];
+
+ // checks if the propety name is present
+ const is_name_present = expected_properties.every(prop => req.body.hasOwnProperty(prop));
+
+ if (!is_name_present) {
+ // according to the stakeholder, if the search string is empty, the system should display all registered restaurants
+ res.json(restaurants_);
+ }
+ else {
+ // according to the stakeholder, it is necessary to show all restaurants that contain the name given as a substring in their own names
+ const regex = new RegExp(req.body.name, 'i');
+
+ const matched_restaurants = restaurants_.filter(restaurant => regex.test(restaurant.name));
+
+ if (matched_restaurants.length === 0) {
+ return res.status(404).json({ error: `Nenhum restaurante contém "${req.body.name}" no nome` });
+ }
+ else {
+ // Return the matched restaurants
+ return res.json(matched_restaurants);
+ }
+ }
+}
+
+module.exports = {
+ search_get
+};
diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js
new file mode 100644
index 0000000000..15ffcf3123
--- /dev/null
+++ b/backend/controllers/userController.js
@@ -0,0 +1,290 @@
+const bcrypt = require('bcrypt')
+const jwt = require('jsonwebtoken')
+
+const User = require('../models/User')
+
+const secret = "gjnhawrgohuqwjkrfnb1o3i4y1230984u35nkrwfvdfgbrty"
+
+
+const user_signup = async (req, res) => {
+ //req.body = JSON.parse(JSON.stringify(req.body))
+ let { name, email, password } = req.body;
+
+ name = name.trim();
+ email = email.trim();
+ password = password.trim();
+
+
+ if (name == "" || email == "" || password == "") {
+ return res.status(400).json({
+ status: "FAILURE",
+ message: "Empty input fields"
+ });
+ }
+
+ if (!/^[a-zA-Z]*$/.test(name)) {
+ return res.status(400).json({
+ status: "FAILURE",
+ message: "Invalid name"
+ });
+ }
+
+ if (!/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email)) {
+ return res.status(400).json({
+ status: "FAILURE",
+ message: "Invalid email"
+ });
+ }
+
+ const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+[\]{};':"\\|,.<>?]).{8,}$/;
+ if (!passwordRegex.test(password)) {
+ return res.status(400).json({ error: 'A senha deve conter no mínimo 1 caracter maiúsculo, 1 caracter minúsculo, 1 simbolo especial e tamanho de pelo menos 8.' });
+ }
+
+ try {
+ const existingUser = await User.findOne({ email });
+
+ if (existingUser) {
+ return res.json({
+ status: "FAILURE",
+ message: "User with this email already exists"
+ });
+ }
+
+ const saltRounds = 10;
+ const hashedPassword = await bcrypt.hash(password, saltRounds);
+ const newUser = new User({
+ name,
+ email,
+ password: hashedPassword
+ });
+ await newUser.save();
+
+ return res.json({
+ status: "SUCCESS",
+ message: "Registration successful",
+ data: newUser
+ });
+ } catch (error) {
+ console.error(error);
+ return res.status(500).json({
+ status: "FAILURE",
+ message: "Internal server error"
+ });
+ }
+};
+
+const user_signin = async (req, res) => {
+ let { email, password } = req.body;
+ email = email.trim();
+ password = password.trim();
+
+ if (email == "" || password == "") {
+ return res.status(400).json({
+ status: "FAILURE",
+ message: "Empty credentials supplied"
+ });
+ }
+
+ try {
+ const user = await User.findOne({ email });
+
+ if (!user) {
+ return res.status(401).json({
+ status: "FAILURE",
+ message: "Invalid credentials"
+ });
+ }
+
+ const hashedPassword = user.password;
+ const match = await bcrypt.compare(password, hashedPassword);
+
+ if (match) {
+ const token = jwt.sign({ userId: user._id }, 'your-secret-key', { expiresIn: '1h' });
+
+ return res.json({
+ status: "SUCCESS",
+ message: "Signin successful",
+ token:token,
+ data: user
+ });
+ } else {
+ return res.status(401).json({
+ status: "FAILURE",
+ message: "Invalid password"
+ });
+ }
+ } catch (error) {
+ console.error(error);
+ return res.status(500).json({
+ status: "FAILURE",
+ message: "Internal server error"
+ });
+ }
+};
+
+
+const getAll = async (req, res) => {
+ const users = await User.find()
+
+ if(users.length === 0){
+ return res.status(404).json({ error: 'Ainda não há usuário cadastrados' })
+ }
+
+ res.json(users)
+}
+
+const getUser = async (req, res) => {
+ const user = await User.findById(req.params.id)
+ if (!user) {
+ return res.status(404).json({ error: 'Usuário não encontrado' })
+ }
+ else{
+ res.json(user)
+ }
+}
+
+const updateUser = async (req, res) => {
+ let user = await User.findById(req.params.id);
+
+ if(!user){
+ return res.status(404).json({ error: 'Usuário não encontrado' })
+ }
+ else{
+
+ req.body = JSON.parse(JSON.stringify(req.body))
+
+ let profileImage = user.profileImage
+ let coverImage = user.coverImage
+
+ console.log(req.body)
+
+ if(req.files.file1 !== undefined){
+ const file1 = req.files.file1[0];
+ profileImage = file1.destination + file1.filename
+ }
+
+ if(req.files.file2 !== undefined){
+ const file2 = req.files.file2[0];
+ coverImage = file2.destination + file2.filename
+ }
+
+ // se achou o usuário
+ //atualiza nome, bio, imagem e capa (se houver para troca)
+ user = await User.findByIdAndUpdate(
+ req.params.id,
+ { name: req.body.name, bio: req.body.bio, profileImage, coverImage },
+ { new: true }
+ );
+ }
+
+ // retorna os dados visíveis do usuário
+ return res.json({
+ name:user.name,
+ bio:user.bio,
+ profileImage:user.profileImage,
+ coverImage:user.coverImage
+ })
+}
+
+const deleteUser = async (req, res) =>{
+ const deletedUser = await User.findById(req.params.id)
+
+ if(!deletedUser){
+ return res.status(404).json({ error: 'Usuário não encontrado' })
+ }else{
+
+ //if the user being deleted and has followers
+ if (deletedUser.followers.length !== 0){
+
+ //for each follower: pull user_page.id from the following list
+ for (const following_id of deletedUser.followers){
+ let user_following = await User.findByIdAndUpdate(
+ {_id: following_id},
+ {$pull : {following: deletedUser.id}},
+ {new: true}
+ )
+
+ }
+ }
+
+ //for each user followed: pull user_page.id from the followers list
+ if (deletedUser.following.length !== 0){
+ for (const followed_id of deletedUser.following){
+ let user_followed = await User.findByIdAndUpdate(
+ {_id: followed_id},
+ {$pull : {followers: deletedUser.id}},
+ {new: true}
+ )
+
+ }
+ }
+
+ const user_deleted = await User.findByIdAndDelete(req.params.id)
+
+ return res.status(200).json({
+ name:deletedUser.name,
+ bio:deletedUser.bio,
+ profileImage:deletedUser.profileImage,
+ coverImage:deletedUser.coverImage
+ });
+ }
+}
+
+const updatePassword = async (req, res) => {
+ let user = await User.findById(req.params.id);
+
+ if(user){
+ //se a senha existir no body corretamente
+ if(req.body.password && req.body.newPassword){
+
+ const match = await bcrypt.compare(req.body.password, user.password);
+
+ //caso a senha seja diferente da atual
+ if(!match){
+ return res.status(400).json({ error: 'Senha atual incorreta.' });
+ }
+
+ //quando a tentativa de troca é para a mesma senha
+ if(req.body.password === req.body.newPassword){
+ return res.status(400).json({ error: 'A nova senha deve ser diferente da senha atual.' });
+ }
+
+ //regex para que a senha tenha requisitos mínimos
+ const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+[\]{};':"\\|,.<>?]).{8,}$/;
+ if (!passwordRegex.test(req.body.newPassword)) {
+ return res.status(404).json({ error: 'A senha deve conter no mínimo 1 caracter maiúsculo, 1 caracter minúsculo, 1 simbolo especial e tamanho de pelo menos 8.' });
+ }
+
+ const saltRounds = 10;
+ const hashedPassword = await bcrypt.hash(req.body.newPassword, saltRounds);
+
+ //atualiza a senha
+ user = await User.findByIdAndUpdate(
+ req.params.id,
+ { password: hashedPassword},
+ { new: true }
+ );
+
+ res.json({
+ message: 'Senha alterada com sucesso!'
+ });
+ }
+ }
+ else{
+ return res.status(404).json({ error: 'Usuário não encontrado' })
+ }
+}
+
+module.exports = {
+
+ user_signup,
+ user_signin,
+ deleteUser,
+ getAll,
+ getUser,
+ updateUser,
+ updatePassword
+
+};
+
diff --git a/backend/jest.config.js b/backend/jest.config.js
new file mode 100644
index 0000000000..4d2f3d3eaf
--- /dev/null
+++ b/backend/jest.config.js
@@ -0,0 +1,20 @@
+module.exports = {
+
+ testTimeout: 20000,
+
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ moduleFileExtensions: ['js', 'json', 'ts'],
+ rootDir: '',
+ testRegex: '.steps.ts$',
+ transform: {
+
+ '^.+\\.(t|j)s$': 'ts-jest',
+
+ },
+
+ detectOpenHandles: true
+
+ //setupFilesAfterEnv: ['./setupTests.ts'],
+
+ };
\ No newline at end of file
diff --git a/backend/models/Forum.js b/backend/models/Forum.js
new file mode 100644
index 0000000000..ada4ffeb47
--- /dev/null
+++ b/backend/models/Forum.js
@@ -0,0 +1,47 @@
+const mongoose = require('mongoose');
+const Schema = mongoose.Schema;
+
+const CommentSchema = new Schema({
+ content: {
+ type: String,
+ required: true
+ },
+ author: {
+ type: Schema.Types.ObjectId,
+ ref: 'User', // Referência ao modelo de usuário
+ required: true
+ },
+ timestamp: {
+ type: Date,
+ default: Date.now
+ }
+});
+
+const PostSchema = new Schema({
+ title: {
+ type: String,
+ required: true
+ },
+ content: {
+ type: String,
+ required: true
+ },
+ author: {
+ type: Schema.Types.ObjectId,
+ ref: 'User', // Referência ao modelo de usuário
+ required: true
+ },
+ attachedImg: {
+ type: String,
+ required: false
+ },
+ comments: [CommentSchema], // Subdocumento para armazenar os comentários
+ timestamp: {
+ type: Date,
+ default: Date.now
+ }
+});
+
+const Forum = mongoose.model('Forum', PostSchema);
+
+module.exports = Forum;
diff --git a/backend/models/Rating.js b/backend/models/Rating.js
new file mode 100644
index 0000000000..d9489d2601
--- /dev/null
+++ b/backend/models/Rating.js
@@ -0,0 +1,23 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+
+const RatingSchema = new Schema({
+ user: {
+ type: Schema.Types.ObjectId,
+ ref:"User",
+ required: true
+ },
+ restaurant: {
+ type: Schema.Types.ObjectId,
+ ref:"Restaurant",
+ required: true
+ },
+ rating: {
+ type: Number,
+ required: true
+ },
+})
+
+const Rating = mongoose.model("Rating", RatingSchema)
+
+module.exports = Rating
\ No newline at end of file
diff --git a/backend/models/Restaurant.js b/backend/models/Restaurant.js
new file mode 100644
index 0000000000..d06e05bc96
--- /dev/null
+++ b/backend/models/Restaurant.js
@@ -0,0 +1,51 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+
+const RestaurantSchema = new Schema({
+ name: {
+ type: String,
+ required: true
+ },
+ address: {
+ city: {
+ type: String,
+ required: true
+ },
+ neighborhood: {
+ type: String,
+ required: true
+ },
+ street: {
+ type: String,
+ required: true
+ },
+ number: {
+ type: String,
+ required: true
+ }
+ },
+ typeOfFood: {
+ type: String,
+ required: true
+ },
+ site: {
+ type: String,
+ required: false
+ },
+ timestamp: {
+ type: String,
+ default: Date.now()
+ },
+ profileImage: {
+ type: String,
+ required: false
+ },
+ coverImage: {
+ type: String,
+ required: false
+ }
+})
+
+const Restaurant = mongoose.model("Restaurant", RestaurantSchema)
+
+module.exports = Restaurant
\ No newline at end of file
diff --git a/backend/models/Review.js b/backend/models/Review.js
new file mode 100644
index 0000000000..fabc44cecf
--- /dev/null
+++ b/backend/models/Review.js
@@ -0,0 +1,56 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+
+const ReviewSchema = new Schema({
+ title: {
+ type: String,
+ required: true
+ },
+ user: {
+ type: Schema.Types.ObjectId,
+ ref:"User",
+ required: true
+ },
+ restaurant: {
+ type: Schema.Types.ObjectId,
+ ref:"Restaurant",
+ required: true
+ },
+ rating: {
+ type: Number,
+ required: true
+ },
+ text: {
+ type: String,
+ required: true
+ },
+ sabor: {
+ type: Number,
+ required: false
+ },
+ atendimento: {
+ type: Number,
+ required: false
+ },
+ tempoDeEspera: {
+ type: Number,
+ required: false
+ },
+ preco: {
+ type: Number,
+ required: false
+ },
+ created: { type: Date, default: Date.now },
+ likes: {
+ type: Number,
+ default: 0
+ },
+ dislikes: {
+ type: Number,
+ default: 0
+ },
+})
+
+const Review = mongoose.model("Review", ReviewSchema)
+
+module.exports = Review
diff --git a/backend/models/User.js b/backend/models/User.js
new file mode 100644
index 0000000000..a9961576cc
--- /dev/null
+++ b/backend/models/User.js
@@ -0,0 +1,52 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+
+const UserSchema = new Schema({
+ name: {
+ type: String,
+ required: true,
+ trim: true
+ },
+
+ email: {
+ type: String,
+ required: true,
+ trim: true
+ },
+
+ password: {
+ type: String,
+ required: true,
+ },
+
+ newPassword: {
+ type: String,
+ required: false
+ },
+
+ bio: {
+ type: String,
+ required: false
+ },
+
+ profileImage:{
+ type: String
+ },
+
+ coverImage:{
+ type: String
+
+ },
+
+ following:{
+ type: Array
+ },
+
+ followers: {
+ type: Array
+ },
+})
+
+const User = mongoose.model("User", UserSchema)
+
+module.exports = User
\ No newline at end of file
diff --git a/backend/models/list.js b/backend/models/list.js
new file mode 100644
index 0000000000..8418e05f3f
--- /dev/null
+++ b/backend/models/list.js
@@ -0,0 +1,27 @@
+const mongoose = require('mongoose')
+const Schema = mongoose.Schema
+
+const ListSchema = new Schema({
+ name:{
+ type: String,
+ required: true
+ },
+
+ description:{
+ type: String,
+ },
+
+ author:{
+ type: String,
+ },
+
+ restaurants:{
+ type: [{type: Schema.Types.ObjectId, ref:"Restaurant"}],
+ required: true
+ }
+
+})
+
+
+const List = mongoose.model("List", ListSchema)
+module.exports = List
diff --git a/backend/package.json b/backend/package.json
new file mode 100644
index 0000000000..622c4df117
--- /dev/null
+++ b/backend/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "backend",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "jest --detectOpenHandles",
+ "start": "node server",
+ "dev": "nodemon server"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "bcrypt": "^5.1.1",
+ "cookie-parser": "^1.4.6",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.5",
+ "express": "^4.18.3",
+ "i": "^0.3.7",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.2.1",
+ "multer": "^1.4.5-lts.1",
+ "nodemailer": "^6.9.11",
+ "npm": "^10.5.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^29.5.12",
+ "@types/node": "^20.11.19",
+ "axios": "^1.6.7",
+ "jest-cucumber": "^3.0.1",
+ "nodemon": "^3.1.0",
+ "ts-jest": "^29.1.2",
+ "typescript": "^5.3.3"
+ }
+}
diff --git a/backend/routes/feedRouter.js b/backend/routes/feedRouter.js
new file mode 100644
index 0000000000..f4dc2fd909
--- /dev/null
+++ b/backend/routes/feedRouter.js
@@ -0,0 +1,15 @@
+const express = require("express")
+const router = express.Router()
+
+const FeedController = require("../controllers/feedController")
+
+// necessary to show random restaurants
+router.get("/random_restaurants", FeedController.get_random_restaurants);
+
+// necessary to show random reviews
+router.get("/random_reviews", FeedController.get_random_reviews);
+
+// necessary to show 5 most liked reviews
+router.get("/most_liked_reviews", FeedController.get_most_liked_reviews);
+
+module.exports = router
diff --git a/backend/routes/followersRouter.js b/backend/routes/followersRouter.js
new file mode 100644
index 0000000000..e5a1c5c8db
--- /dev/null
+++ b/backend/routes/followersRouter.js
@@ -0,0 +1,14 @@
+const express = require("express")
+const router = express.Router()
+
+const FollowerController = require("../controllers/followersController")
+
+router.get('/followers/:id', FollowerController.user_followers_get)
+
+router.get('/following/:id', FollowerController.user_following_get)
+
+router.put('/follow/:idp/:idl', FollowerController.user_follow)
+
+router.put('/unfollow/:idp/:idl', FollowerController.user_unfollow)
+
+module.exports = router
\ No newline at end of file
diff --git a/backend/routes/forumRouter.js b/backend/routes/forumRouter.js
new file mode 100644
index 0000000000..86d7b71b5d
--- /dev/null
+++ b/backend/routes/forumRouter.js
@@ -0,0 +1,26 @@
+const express = require("express");
+const router = express.Router();
+
+const upload = require("../config/multer");
+
+const ForumController = require("../controllers/forumController");
+
+// Rotas para Posts do Fórum
+router.get('/', ForumController.forumPosts_get);
+
+router.get('/:postId', ForumController.singleForumPost_get);
+
+router.post('/create', upload.single("file"), ForumController.forumPost_create);
+
+router.put('/edit/:postId', ForumController.forumPost_edit);
+
+router.delete('/delete/:postId', ForumController.forumPost_delete);
+
+// Rotas para Comentários do Fórum
+router.post('/:postId/comments/create', ForumController.forumComment_create);
+
+router.put('/:postId/comments/edit/:commentId', ForumController.forumComment_edit);
+
+router.delete('/:postId/comments/delete/:commentId', ForumController.forumComment_delete);
+
+module.exports = router;
diff --git a/backend/routes/listRouter.js b/backend/routes/listRouter.js
new file mode 100644
index 0000000000..518521b367
--- /dev/null
+++ b/backend/routes/listRouter.js
@@ -0,0 +1,18 @@
+const express = require("express")
+const router = express.Router()
+
+const upload = require("../config/multer")
+
+const ListsController = require("../controllers/listController")
+
+router.get('/', ListsController.list_get_all)
+
+router.get('/:id', ListsController.list_get)
+
+router.put('/edit/:id', ListsController.list_edit)
+
+router.post('/create/:id', ListsController.list_create)
+
+router.delete('/delete/:id', ListsController.list_delete)
+
+module.exports = router
diff --git a/backend/routes/ratingRouter.js b/backend/routes/ratingRouter.js
new file mode 100644
index 0000000000..b571c371f7
--- /dev/null
+++ b/backend/routes/ratingRouter.js
@@ -0,0 +1,16 @@
+const express = require("express")
+const router = express.Router()
+
+const RatingController = require("../controllers/ratingController")
+
+router.post('/:idrest/:iduser/create', RatingController.rating_post)
+
+router.put('/:idrest/:iduser/edit', RatingController.rating_edit)
+
+router.get('/:idrest/avg', RatingController.rating_avg)
+
+router.get('/:idrest', RatingController.rating_list)
+
+router.get('/:idrest/:iduser', RatingController.rating_get)
+
+module.exports = router
diff --git a/backend/routes/restaurantRouter.js b/backend/routes/restaurantRouter.js
new file mode 100644
index 0000000000..0e84f522e5
--- /dev/null
+++ b/backend/routes/restaurantRouter.js
@@ -0,0 +1,19 @@
+const express = require("express")
+const router = express.Router()
+
+const upload = require("../config/multer")
+
+const RestaurantController = require("../controllers/restaurantController")
+
+router.get('/', RestaurantController.restaurants_get)
+
+router.get('/:id', RestaurantController.restaurant_profile_get)
+
+router.post('/create', upload.fields([{ name: 'file1', maxCount: 1 }, { name: 'file2', maxCount: 1 }]), RestaurantController.restaurant_create)
+
+router.put('/edit/:id', upload.fields([{ name: 'file1', maxCount: 1 }, { name: 'file2', maxCount: 1 }]), RestaurantController.restaurant_edit)
+
+router.delete('/delete/:id', RestaurantController.restaurant_delete)
+
+module.exports = router
+
diff --git a/backend/routes/reviewRouter.js b/backend/routes/reviewRouter.js
new file mode 100644
index 0000000000..aaab32c92a
--- /dev/null
+++ b/backend/routes/reviewRouter.js
@@ -0,0 +1,18 @@
+const express = require("express")
+const router = express.Router()
+
+const ReviewController = require("../controllers/reviewController")
+
+router.get('/:idrest', ReviewController.review_show)
+
+router.get('/:idrest/:iduser', ReviewController.review_get)
+
+router.post('/:idrest/:iduser/create', ReviewController.review_post)
+
+router.put('/:idrest/:iduser/edit', ReviewController.review_edit)
+
+router.delete('/:idrest/:iduser/delete', ReviewController.review_delete)
+
+router.get('/user/:iduser', ReviewController.review_user)
+
+module.exports = router
diff --git a/backend/routes/searchesRouter.js b/backend/routes/searchesRouter.js
new file mode 100644
index 0000000000..4c31558b0d
--- /dev/null
+++ b/backend/routes/searchesRouter.js
@@ -0,0 +1,9 @@
+const express = require("express")
+const router = express.Router()
+
+const SearchController = require("../controllers/searchesController")
+
+// necessary to do the search for content
+router.get('/search_restaurant', SearchController.search_get)
+
+module.exports = router
diff --git a/backend/routes/userRouter.js b/backend/routes/userRouter.js
new file mode 100644
index 0000000000..9dcd1f1a29
--- /dev/null
+++ b/backend/routes/userRouter.js
@@ -0,0 +1,25 @@
+const express = require('express');
+const router = express.Router();
+
+//criptografia da senha
+const bcrypt = require('bcrypt');
+// nao tenho certeza se esta certo
+const upload = require('../config/multer')
+
+const UserController = require("../controllers/userController")
+
+router.post('/signup', UserController.user_signup)
+
+router.post('/signin', UserController.user_signin)
+
+router.get('/', UserController.getAll)
+
+router.get('/:id', UserController.getUser)
+
+router.put('/edit/:id', upload.fields([{ name: 'file1', maxCount: 1 }, { name: 'file2', maxCount: 1 }]), UserController.updateUser)
+
+router.put('/editPass/:id', UserController.updatePassword)
+
+router.delete('/delete/:id', UserController.deleteUser)
+
+module.exports = router
diff --git a/backend/server.js b/backend/server.js
new file mode 100644
index 0000000000..fa7f702b9d
--- /dev/null
+++ b/backend/server.js
@@ -0,0 +1,61 @@
+// import required packages
+const express = require('express')
+const mongoose = require('mongoose')
+require("dotenv").config()
+const cors = require('cors')
+const jwt = require('jsonwebtoken')
+
+
+
+// import routers
+const restaurantRouter = require("./routes/restaurantRouter")
+const forumRouter = require("./routes/forumRouter")
+const userRouter = require("./routes/userRouter")
+const followersRouter = require("./routes/followersRouter")
+const reviewRouter = require("./routes/reviewRouter")
+const ratingRouter = require("./routes/ratingRouter")
+const listRouter = require("./routes/listRouter")
+const feedRouter = require("./routes/feedRouter")
+const searchesRouter = require("./routes/searchesRouter")
+
+
+// use the PORT in .env or 3000 if it does not exist
+const port = process.env.PORT || 3000
+
+// create express app
+const app = express()
+
+// use middleware to parse json
+app.use(express.json())
+
+// include Access-Control-Allow-Origin headers
+app.use(cors())
+
+
+
+app.use('/uploads', express.static(__dirname + '/uploads'))
+
+// connect to data base
+const run = async () => {
+ await mongoose.connect(`mongodb://localhost:27017/${process.env.DBNAME}`);
+ console.log("Connected to data base")
+}
+
+run()
+.catch((err) => console.error(err))
+
+
+// start app
+app.listen(port, () => console.log("Server started on port 3001"))
+
+// Routes
+app.use("/restaurants", restaurantRouter)
+app.use("/forum", forumRouter)
+app.use("/users", userRouter)
+app.use("/users", followersRouter)
+app.use("/reviews", reviewRouter)
+app.use("/ratings", ratingRouter)
+app.use("/lists", listRouter)
+app.use("/feed", feedRouter)
+app.use("/searches", searchesRouter)
+
diff --git a/backend/tests/controllers/common.ts b/backend/tests/controllers/common.ts
new file mode 100644
index 0000000000..3dc71b51b2
--- /dev/null
+++ b/backend/tests/controllers/common.ts
@@ -0,0 +1,23 @@
+const mongoose = require('mongoose')
+require("dotenv").config()
+
+export async function connectDBForTesting() {
+ try {
+ const dbUri = "mongodb://localhost:27017";
+ const dbName = process.env.DBNAME;
+ await mongoose.connect(dbUri, {
+ dbName,
+ autoCreate: true,
+ });
+ } catch (error) {
+ console.log("DB connect error");
+ }
+}
+
+export async function disconnectDBForTesting() {
+ try {
+ await mongoose.connection.close();
+ } catch (error) {
+ console.log("DB disconnect error");
+ }
+}
\ No newline at end of file
diff --git a/backend/tests/controllers/followers/listFollowers.steps.ts b/backend/tests/controllers/followers/listFollowers.steps.ts
new file mode 100644
index 0000000000..11895692ad
--- /dev/null
+++ b/backend/tests/controllers/followers/listFollowers.steps.ts
@@ -0,0 +1,69 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require('../../../models/User.js');
+const feature = loadFeature('tests/features/followers/listFollowers.feature');
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ let response: AxiosResponse
+ let user1: typeof User
+ let user2: typeof User
+ let user3: typeof User
+ let user4: typeof User
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ test('Pegar lista de seguidores', ({ given, when, then }) => {
+ given(/^o usuário com id "(.*)" está armazenado no sistema com a lista de seguidores "(.*)", "(.*)", "(.*)"$/,
+ async (id1, id2, id3, id4) => {
+ let user1 = await User.findByIdAndUpdate(
+ {_id: id1},
+ {followers: [id2, id3, id4]},
+ {new: true}
+ )
+ let user2 = await User.findByIdAndUpdate(
+ {_id: id2},
+ {following: [id1]},
+ {new: true}
+ )
+ let user3 = await User.findByIdAndUpdate(
+ {_id: id3},
+ {following: [id1]},
+ {new: true}
+ )
+ let user4 = await User.findByIdAndUpdate(
+ {_id: id4},
+ {following: [id1]},
+ {new: true}
+ )
+ });
+
+ when(/^fizer uma requisição GET com rota "(.*)"$/, async (path) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.log('Error during HTTP request:', error)
+ }
+ });
+
+ then(/^o status do sistema é (\d+)$/, async (status) => {
+ expect(response.status).toBe(Number(status))
+
+ });
+
+ then(/^o sistema retorna um JSON com a lista "(.*)", "(.*)", "(.*)"$/,
+ async (id1, id2, id3) => {
+ expect(response.data).toEqual(expect.arrayContaining([id1, id2, id3]))
+ });
+ });
+})
\ No newline at end of file
diff --git a/backend/tests/controllers/followers/listFollowersUnit.steps.ts b/backend/tests/controllers/followers/listFollowersUnit.steps.ts
new file mode 100644
index 0000000000..c18fe613ed
--- /dev/null
+++ b/backend/tests/controllers/followers/listFollowersUnit.steps.ts
@@ -0,0 +1,44 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require('../../../models/User.js');
+const feature = loadFeature('tests/features/followers/listFollowersUnit.feature');
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ let user: typeof User
+ let user1: typeof User
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ user1 = new User();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ test('Pegar lista de seguidores - teste unitário', ({ given, when, then }) => {
+ given(/^o usuário com id "(.*)" está armazenado no sistema com a lista de seguidores "(.*)", "(.*)", "(.*)"$/,
+ async (id1, id2, id3, id4) => {
+ let user1 = await User.findByIdAndUpdate(
+ {_id: id1},
+ {followers: [id2, id3, id4]},
+ {new: true}
+ )
+ });
+
+ when(/^fizer a busca pela lista de seguidores do usuário "(.*)"$/, async (id) => {
+ user = await User.findById(id)
+ });
+
+
+ then(/^o sistema retorna um JSON com a lista "(.*)", "(.*)", "(.*)"$/,
+ async (id1, id2, id3) => {
+ expect(user.followers).toEqual(expect.arrayContaining([id1, id2, id3]))
+ });
+ });
+})
\ No newline at end of file
diff --git a/backend/tests/controllers/followers/listFollowing.steps.ts b/backend/tests/controllers/followers/listFollowing.steps.ts
new file mode 100644
index 0000000000..9d940f48fd
--- /dev/null
+++ b/backend/tests/controllers/followers/listFollowing.steps.ts
@@ -0,0 +1,69 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require('../../../models/User.js');
+const feature = loadFeature('tests/features/followers/listFollowing.feature');
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ let response: AxiosResponse
+ let user1: typeof User
+ let user2: typeof User
+ let user3: typeof User
+ let user4: typeof User
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ test('Pegar lista de usuários que segue', ({ given, when, then }) => {
+ given(/^o usuário com o id "(.*)" está armazenado no sistema com a lista "(.*)", "(.*)", "(.*)" de usuários que segue$/,
+ async (id1, id2, id3, id4) => {
+ let user1 = await User.findByIdAndUpdate(
+ {_id: id1},
+ {following: [id2, id3, id4]},
+ {new: true}
+ )
+ let user2 = await User.findByIdAndUpdate(
+ {_id: id2},
+ {followers: [id1]},
+ {new: true}
+ )
+ let user3 = await User.findByIdAndUpdate(
+ {_id: id3},
+ {followers: [id1]},
+ {new: true}
+ )
+ let user4 = await User.findByIdAndUpdate(
+ {_id: id4},
+ {followers: [id1]},
+ {new: true}
+ )
+ });
+
+ when(/^fizer uma requisição GET com rota "(.*)"$/, async (path) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.log('Error during HTTP request:', error)
+ }
+ });
+
+ then(/^o status do sistema é (\d+)$/, async (status) => {
+ expect(response.status).toBe(Number(status))
+
+ });
+
+ then(/^o sistema retorna um JSON com a lista "(.*)", "(.*)", "(.*)"$/,
+ async (id1, id2, id3) => {
+ expect(response.data).toEqual(expect.arrayContaining([id1, id2, id3]))
+ });
+ });
+})
\ No newline at end of file
diff --git a/backend/tests/controllers/followers/seguirUsuario.steps.ts b/backend/tests/controllers/followers/seguirUsuario.steps.ts
new file mode 100644
index 0000000000..ee63eb211c
--- /dev/null
+++ b/backend/tests/controllers/followers/seguirUsuario.steps.ts
@@ -0,0 +1,70 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require('../../../models/User.js');
+const feature = loadFeature('tests/features/followers/seguirUsuario.feature');
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ let response: AxiosResponse
+ let user1: typeof User
+ let user2: typeof User
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ test('Seguir um usuário', ({ given, when, then }) => {
+ given(/^o usuário com id "(.*)" está armazenado no sistema com a lista de usuários que segue vazia$/, async (id) => {
+ let user1 = await User.findByIdAndUpdate(
+ {_id: id},
+ {following: []},
+ {new: true}
+ )
+ });
+
+ given(/^o usuário com o id "(.*)" está armazenado no sistema com a lista de seguidores vazia e com e-mail "(.*)"$/,
+ async (id, email) => {
+ let user2 = await User.findByIdAndUpdate(
+ {_id: id},
+ {followers: []},
+ {email: email},
+ {new: true}
+ )
+ });
+
+ when(/^fizer uma requisição PUT com rota "(.*)" e o body contendo o id "(.*)"$/, async (path, id_follower) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {_id: id_follower})
+ } catch (error) {
+ console.log('Error during HTTP request:', error)
+ }
+ });
+
+ then(/^o status do sistema é (\d+)$/, async (status) => {
+ expect(response.status).toBe(Number(status))
+
+ });
+
+ then(/^retorna um JSON com os dados do usuário com o id "(.*)" que tem a lista de seguidores "(.*)"$/, async (id_followed, list_followers) => {
+ expect(response.data.followed.id).toBe(id_followed)
+ expect(response.data.followed.followers).toEqual(expect.arrayContaining([list_followers]))
+ });
+
+ then(/^com os dados do usuário com id "(.*)" tem a lista de usuários que segue "(.*)"$/, async (id_follower, list_following) => {
+ expect(response.data.follower.id).toBe(id_follower)
+ expect(response.data.follower.following).toEqual(expect.arrayContaining([list_following]))
+ });
+
+ then(/^a mensagem enviada para o e-mail cadastrado do usuário seguido tem status "(.*)"$/, async (email_status) => {
+ expect(response.data.status_email).toBe(email_status)
+ });
+ });
+ });
\ No newline at end of file
diff --git a/backend/tests/controllers/followers/unfollow.steps.ts b/backend/tests/controllers/followers/unfollow.steps.ts
new file mode 100644
index 0000000000..2809453f69
--- /dev/null
+++ b/backend/tests/controllers/followers/unfollow.steps.ts
@@ -0,0 +1,66 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require('../../../models/User.js');
+const feature = loadFeature('tests/features/followers/unfollow.feature');
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ let response: AxiosResponse
+ let user1: typeof User
+ let user2: typeof User
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+
+ test('Deixar de seguir um usuário', ({ given, when, then }) => {
+ given(/^o usuário com id "(.*)" está armazenado no sistema com a lista de usuários que segue "(.*)"$/, async (id_following, id_followed) => {
+ let user1 = await User.findByIdAndUpdate(
+ {_id: id_following},
+ {following: [id_followed]},
+ {new: true}
+ )
+ });
+
+ given(/^o usuário com o id "(.*)" está armazenado no sistema com a lista de seguidores "(.*)"$/,
+ async (id_followed, id_following) => {
+ let user2 = await User.findByIdAndUpdate(
+ {_id: id_followed},
+ {followers: [id_following]},
+ {new: true}
+ )
+ });
+
+ when(/^fizer uma requisição PUT com rota "(.*)" e o body contendo o id "(.*)"$/, async (path, id_follower) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {_id: id_follower})
+ } catch (error) {
+ console.log('Error during HTTP request:', error)
+ }
+ });
+
+ then(/^o status do sistema é (\d+)$/, async (status) => {
+ expect(response.status).toBe(Number(status))
+
+ });
+
+ then(/^retorna um JSON contendo o id "(.*)" e a lista de seguidores vazia$/, async (id_followed) => {
+ expect(response.data.unfollowed.id).toBe(id_followed)
+ expect(response.data.unfollowed.followers).toEqual([])
+ });
+
+ then(/^contendo o id "(.*)" e a lista de usuários que segue vazia$/, async (id_follower) => {
+ expect(response.data.unfollower.id).toBe(id_follower)
+ expect(response.data.unfollower.following).toEqual([])
+ });
+ });
+ });
\ No newline at end of file
diff --git a/backend/tests/controllers/lists/list1.steps.ts b/backend/tests/controllers/lists/list1.steps.ts
new file mode 100644
index 0000000000..4074681b4c
--- /dev/null
+++ b/backend/tests/controllers/lists/list1.steps.ts
@@ -0,0 +1,69 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+const List = require("../../../models/List")
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/lists/list1.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Criar lista com sucesso', ({ given, when, then, and }) => {
+ given(/^um usuário de nome "(.*)" está logado com ID "(.*)"$/, async (nameuser, iduser) => {
+
+ const user = await User.findOne({_id: iduser, name:nameuser})
+
+ expect(user).toEqual((
+ expect.objectContaining({
+ name: nameuser
+ })
+ ))
+
+ })
+ when(/^uma requisição POST é enviada para "(.*)" com o nome "(.*)"$/, async (path, title) => {
+
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`, {name: title})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^um JSON com o nome do autor "(.*)" e nome da lista "(.*)" é retornado$/, async (nameautor, namelist) =>{
+ expect(response.data).toEqual((
+ expect.objectContaining({
+ author: nameautor,
+ name: namelist
+ })
+ ))
+ })
+ and(/^a lista do usuário com ID "(.*)" com nome "(.*)" pode ser encontrada no banco de dados$/, async (userid, title) => {
+ const user = await User.findById(userid)
+ const list = await List.findOne({author: user.name, name: title})
+
+ expect(list).toEqual(
+ expect.objectContaining({
+ name: title,
+ author: user.name
+ })
+ )})
+
+
+ });
+});
diff --git a/backend/tests/controllers/lists/list2.steps.ts b/backend/tests/controllers/lists/list2.steps.ts
new file mode 100644
index 0000000000..ebfa3fabad
--- /dev/null
+++ b/backend/tests/controllers/lists/list2.steps.ts
@@ -0,0 +1,55 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+const List = require("../../../models/List")
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/lists/list2.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Excluir lista por ID', ({ given, when, then, and }) => {
+ given(/^existe uma lista de restaurantes com ID "(.*)" com autor de ID "(.*)" e nome "(.*)"$/, async (idlist, iduser, namelist) => {
+
+ const user = await User.findById(iduser)
+ const list = await List.findById(idlist)
+
+ expect(list).toEqual((
+ expect.objectContaining({
+ name: namelist,
+ author: user.name
+ })
+ ))
+
+ })
+ when(/^uma requisição DELETE é enviada para "(.*)"$/, async (path) => {
+
+ try {
+ response = await axios.delete(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^a lista com ID "(.*)" não pode ser encontrada no banco de dados$/, async (idlist) =>{
+ const list = await List.findById(idlist)
+ expect(list).toBe(null)
+ })
+
+ });
+});
diff --git a/backend/tests/controllers/lists/list3.steps.ts b/backend/tests/controllers/lists/list3.steps.ts
new file mode 100644
index 0000000000..fa02c61e0b
--- /dev/null
+++ b/backend/tests/controllers/lists/list3.steps.ts
@@ -0,0 +1,53 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+const List = require("../../../models/List")
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/lists/list3.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Tentar criar uma lista sem nome', ({ given, when, then, and }) => {
+ given(/^o usuário de ID "(.*)" está logado$/, async (iduser) => {
+
+ const user = await User.findById(iduser)
+
+ expect(user).toEqual((
+ expect.objectContaining({
+ name: user.name
+ })
+ ))
+
+ })
+ when(/^uma requisição POST é enviada para "(.*)" com nenhum nome e descrição "(.*)"$/, async (path, descricao) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`, {description: descricao})
+ } catch (error) {
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(response).toBe(undefined)
+ })
+ and(/^a lista sem nome não pode ser encontrada no banco de dados com autor de ID "(.*)"$/, async (idautor) =>{
+ const user = await User.findById(idautor)
+ const list = await List.findOne({author: user.name, name: ""})
+
+ expect(list).toBe(null)
+ })
+
+ });
+});
diff --git a/backend/tests/controllers/lists/list4.steps.ts b/backend/tests/controllers/lists/list4.steps.ts
new file mode 100644
index 0000000000..32f72ebb46
--- /dev/null
+++ b/backend/tests/controllers/lists/list4.steps.ts
@@ -0,0 +1,58 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+const List = require("../../../models/List")
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/lists/list4.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Editar o nome de uma lista', ({ given, when, then, and }) => {
+ given(/^existe uma lista de restaurantes com ID "(.*)" e nome "(.*)" com autor de nome "(.*)"$/, async (idlist, titulo, authorname) => {
+
+ const list = await List.findOne({name: titulo, _id: idlist})
+
+ expect(list).toEqual((
+ expect.objectContaining({
+ author: authorname
+ })
+ ))
+
+ })
+ when(/^uma requisição PUT é enviada para "(.*)" com o novo nome "(.*)"$/, async (path, newname) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {name: newname})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^a lista com ID "(.*)" é atualizada com nome "(.*)" no banco de dados$/, async (idlist, newname) =>{
+ const list = await List.findById(idlist)
+
+ expect(list).toEqual((
+ expect.objectContaining({
+ name: newname
+ })
+ ))
+ })
+
+ });
+});
diff --git a/backend/tests/controllers/login/login1.steps.ts b/backend/tests/controllers/login/login1.steps.ts
new file mode 100644
index 0000000000..7f01e206f1
--- /dev/null
+++ b/backend/tests/controllers/login/login1.steps.ts
@@ -0,0 +1,56 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+const bcrypt = require('bcrypt');
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/login/login1.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('criar usuário', ({ given, when, then, and }) => {
+ given(/^não existe um usuário cadastrado com o nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+
+ const user = await User.findOne({name: name, email: email, password: password})
+
+ expect(user).toBe(
+ null
+ )
+
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com o nome "(.*)", email "(.*)" e senha "(.*)"$/, async (path,name,email,password) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`,{name:name,email:email,password:password})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^um usuário é cadastrado com nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+ const user = await User.findOne({name: name, email: email})
+ const verify = await bcrypt.compare(password,user.password)
+ let compare = true
+ if(!verify){
+ compare = false
+ }
+ expect(compare).toBe(true)
+
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/login/login2.steps.ts b/backend/tests/controllers/login/login2.steps.ts
new file mode 100644
index 0000000000..7e000fbe37
--- /dev/null
+++ b/backend/tests/controllers/login/login2.steps.ts
@@ -0,0 +1,58 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/login/login2.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('logar o usuário', ({ given, when, then, and }) => {
+ given(/^existe um usuário com nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+
+ const user = await User.findOne({name: name, email: email})
+
+ expect(email).toBe(
+ user.email
+ )
+
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com o email "(.*)" e senha "(.*)"$/, async (path,email,password) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`,{email:email,password:password})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^um usuário é logado com nome "(.*)" e email "(.*)"$/, async (name,email) => {
+ const user = await User.findOne({name:name,email:email})
+
+ let compare = false
+ if(name == user.name){
+ compare = true
+ }
+
+ expect(compare).toBe(true)
+
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/login/login3.steps.ts b/backend/tests/controllers/login/login3.steps.ts
new file mode 100644
index 0000000000..4171fde941
--- /dev/null
+++ b/backend/tests/controllers/login/login3.steps.ts
@@ -0,0 +1,57 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/login/login3.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('A senha não contém caracteres especiais e letras maiusculas', ({ given, when, then, and }) => {
+ given(/^não existe um usuário cadastrado com o nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+
+ const user = await User.findOne({name: name, email: email, password: password})
+
+ expect(user).toBe(
+ null
+ )
+
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com nome "(.*)", email "(.*)" e senha "(.*)"$/, async (path,name,email,password) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`,{name:name,email:email,password:password})
+ } catch (error) {
+
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(response).toBe(undefined)
+ })
+ and(/^é retornado o aviso "(.*)"$/, async (msg) => {
+
+
+ let compare = false
+ if(msg == "A senha deve conter no mínimo 1 caracter maiúsculo, 1 caracter minúsculo, 1 simbolo especial e tamanho de pelo menos 8."){
+ compare = true
+ }
+
+ expect(compare).toBe(true)
+
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/login/login4.steps.ts b/backend/tests/controllers/login/login4.steps.ts
new file mode 100644
index 0000000000..91f5402879
--- /dev/null
+++ b/backend/tests/controllers/login/login4.steps.ts
@@ -0,0 +1,58 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+const bcrypt = require('bcrypt');
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/login/login4.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('A senha para login está errada', ({ given, when, then, and }) => {
+ given(/^existe um usuário cadastrado com nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+
+ const user = await User.findOne({name: name, email: email})
+
+ expect(email).toBe(
+ email
+ )
+
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com o email "(.*)" e senha "(.*)"$/, async (path,email,password) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`,{email:email,password:password})
+ } catch (error) {
+
+ return
+ }
+ })
+ then(/^o status da resposta é "(.*)"$/, (status) => {
+ expect(response).toBe(undefined)
+ })
+ and(/^é retornado o aviso "(.*)"$/, async (msg) => {
+
+
+ let compare = false
+ if(msg == "Invalid password"){
+ compare = true
+ }
+
+ expect(compare).toBe(true)
+
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/login/login5.steps.ts b/backend/tests/controllers/login/login5.steps.ts
new file mode 100644
index 0000000000..686904c668
--- /dev/null
+++ b/backend/tests/controllers/login/login5.steps.ts
@@ -0,0 +1,60 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+const bcrypt = require('bcrypt');
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/login/login5.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('O email não está no formato adequado', ({ given, when, then, and }) => {
+ given(/^não existe um usuário cadastrado com o nome "(.*)", email "(.*)" e senha "(.*)"$/, async (name,email,password) => {
+
+ const user = await User.findOne({name: name, email: email, password: password})
+
+ expect(user).toBe(
+ null
+ )
+
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com o nome "(.*)", email "(.*)" e senha "(.*)"$/, async (path,name,email,password) => {
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`,{name:name,email:email,password:password})
+ } catch (error) {
+
+ return
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(response).toBe(undefined)
+ })
+ and(/^é retornado o aviso "(.*)"$/, async (name,email,password) => {
+ const user = await User.findOne({name: name, email: email})
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ let compare = false
+ if(!emailRegex.test(email)){
+ compare = true
+
+ }
+
+
+
+ expect(compare).toBe(true)
+
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/restaurant/restaurant_create.steps.ts b/backend/tests/controllers/restaurant/restaurant_create.steps.ts
new file mode 100644
index 0000000000..9321823d9e
--- /dev/null
+++ b/backend/tests/controllers/restaurant/restaurant_create.steps.ts
@@ -0,0 +1,90 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/restaurant/restaurant_create.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('cadastrar restaurante novo', ({ given, when, then, and }) => {
+ given(/^não existe um restaurante cadastrado com nome "(.*)" e endereço "(.*)"$/, async (name, addr) => {
+ const restaurant = await Restaurant.findOne({name : name, address: addressSeparation(addr)})
+
+ expect(restaurant).toBeNull()
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com nome "(.*)", endereço "(.*)" e tipo de comida "(.*)"$/, async (path, name, addr, typeOfFood) => {
+
+ const addressInfo = addressSeparation(addr)
+
+ if (addressInfo !== null) {
+ const { street, number, neighborhood, city } = addressInfo
+
+ response = await axios.post(`${SERVER_URL}${path}`, {
+ name: name,
+ typeOfFood: typeOfFood,
+ address: {
+ street,
+ number,
+ neighborhood,
+ city
+ }
+ })
+ }
+
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^existe um restaurante cadastrado com nome "(.*)", endereço "(.*)" e tipo de comida "(.*)"$/, async (name, addr, typeOfFood) => {
+
+ const addressInfo = addressSeparation(addr)
+
+ if (addressInfo !== null) {
+ const { street, number, neighborhood, city } = addressInfo
+
+
+ const restaurant = await Restaurant.findOne({name : name, 'address.street': street, 'address.number': number, 'address.city': city, 'address.neighborhood': neighborhood, typeOfFood: typeOfFood})
+
+ expect(restaurant).not.toBeNull()
+ }
+ })
+ })
+})
+
+function addressSeparation(addr: string) {
+ // Expressão regular para extrair a rua, número, bairro e cidade
+ var regex = /(.+),\s*(\d+)\s*-\s*(.+),\s*(.+)/;
+
+ // Executar a expressão regular no endereço fornecido
+ var match = addr.match(regex);
+
+ // Verificar se houve correspondência
+ if (match) {
+ // Extrair os grupos correspondentes e retornar o objeto
+ return {
+ street: match[1].trim(),
+ number: match[2].trim(),
+ neighborhood: match[3].trim(),
+ city: match[4].trim()
+ };
+ } else {
+ // Se não houver correspondência, retornar null ou tratar conforme necessário
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/backend/tests/controllers/restaurant/restaurant_create_error.steps.ts b/backend/tests/controllers/restaurant/restaurant_create_error.steps.ts
new file mode 100644
index 0000000000..65d7f50bd3
--- /dev/null
+++ b/backend/tests/controllers/restaurant/restaurant_create_error.steps.ts
@@ -0,0 +1,92 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/restaurant/restaurant_create_error.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('erro ao cadastrar restaurante existente', ({ given, when, then, and }) => {
+ let restaurant: any;
+ given(/^existe um restaurante cadastrado com nome "(.*)" e endereço "(.*)"$/, async (name, addr) => {
+ const addressInfo = addressSeparation(addr)
+
+ if (addressInfo !== null) {
+ const { street, number, neighborhood, city } = addressInfo
+
+
+ restaurant = await Restaurant.findOne({name : name, 'address.street': street, 'address.number': number, 'address.city': city, 'address.neighborhood': neighborhood})
+
+ expect(restaurant).not.toBeNull()
+ }
+
+ expect(restaurant).not.toBeNull()
+ })
+ when(/^uma requisição POST foi enviada para "(.*)" com nome "(.*)", endereço "(.*)" e tipo de comida "(.*)"$/, async (path, name, addr, typeOfFood) => {
+
+ const addressInfo = addressSeparation(addr)
+
+ if (addressInfo !== null) {
+ const { street, number, neighborhood, city } = addressInfo
+
+ response = await axios.post(`${SERVER_URL}${path}`, {
+ name: name,
+ typeOfFood: typeOfFood,
+ address: {
+ street,
+ number,
+ neighborhood,
+ city
+ }
+ }, {
+ validateStatus: (status) => true
+ })
+
+ }
+
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^a resposta é "(.*)"$/, (ans) => {
+ expect(response.data.error).toEqual(ans)})
+ })
+})
+
+function addressSeparation(addr: string) {
+ // Expressão regular para extrair a rua, número, bairro e cidade
+ var regex = /(.+),\s*(\d+)\s*-\s*(.+),\s*(.+)/;
+
+ // Executar a expressão regular no endereço fornecido
+ var match = addr.match(regex);
+
+ // Verificar se houve correspondência
+ if (match) {
+ // Extrair os grupos correspondentes e retornar o objeto
+ return {
+ street: match[1].trim(),
+ number: match[2].trim(),
+ neighborhood: match[3].trim(),
+ city: match[4].trim()
+ };
+ } else {
+ // Se não houver correspondência, retornar null ou tratar conforme necessário
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/backend/tests/controllers/restaurant/restaurant_delete.steps.ts b/backend/tests/controllers/restaurant/restaurant_delete.steps.ts
new file mode 100644
index 0000000000..3c3819ffc8
--- /dev/null
+++ b/backend/tests/controllers/restaurant/restaurant_delete.steps.ts
@@ -0,0 +1,43 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/restaurant/restaurant_delete.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Deletar restaurante por ID', ({ given, when, then, and }) => {
+ given(/^existe um restaurante cadastrado com id "(.*)" e nome "(.*)"$/, async (id, name) => {
+ const restaurant = await Restaurant.findById(id)
+
+ expect(restaurant.name).toBe(name)
+ })
+ when(/^uma requisição DELETE foi enviada para "(.*)"$/, async (path) => {
+ response = await axios.delete(`${SERVER_URL}${path}`)
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^não existe um restaurante cadastrado com id "(.*)" e nome "(.*)"$/, async (id, name) => {
+ const restaurant = await Restaurant.findById(id)
+
+ expect(restaurant).toBeNull()
+ })
+ })
+})
\ No newline at end of file
diff --git a/backend/tests/controllers/restaurant/restaurant_edit.steps.ts b/backend/tests/controllers/restaurant/restaurant_edit.steps.ts
new file mode 100644
index 0000000000..a7e9c820ae
--- /dev/null
+++ b/backend/tests/controllers/restaurant/restaurant_edit.steps.ts
@@ -0,0 +1,55 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/restaurant/restaurant_edit.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Editar restaurante por ID', ({ given, when, then, and }) => {
+ given(/^existe um restaurante cadastrado com id "(.*)" e nome "(.*)"$/, async (id, name) => {
+ const restaurant = await Restaurant.findById(id)
+
+ expect(restaurant.name).toBe(name)
+ })
+ when(/^uma requisição PUT foi enviada para "(.*)" com nome "(.*)"$/, async (path, name) => {
+
+ response = await axios.put(`${SERVER_URL}${path}`, {
+ name: name,
+ address: {
+ city: "São Paulo",
+ neighborhood: "Ibirapuera",
+ street: "Rua legal",
+ number: "1200"
+ },
+ typeOfFood: "Italiana",
+ site: "rangoitaliano.com"
+ })
+
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^existe um restaurante cadastrado com id "(.*)" e nome "(.*)"$/, async (id,name) => {
+ const restaurant = await Restaurant.findById(id)
+
+ expect(restaurant.name).toBe(name)
+ })
+ })
+})
diff --git a/backend/tests/controllers/restaurant/restaurant_get.steps.ts b/backend/tests/controllers/restaurant/restaurant_get.steps.ts
new file mode 100644
index 0000000000..94aca3da36
--- /dev/null
+++ b/backend/tests/controllers/restaurant/restaurant_get.steps.ts
@@ -0,0 +1,50 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/restaurant/restaurant_get.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Obter restaurante por ID', ({ given, when, then, and }) => {
+ given(/^existe um restaurante cadastrado com id "(.*)" e nome "(.*)"$/, async (id, name) => {
+ const restaurant = await Restaurant.findById(id)
+
+ expect(restaurant.name).toBe(name)
+ })
+ when(/^uma requisição GET foi enviada para "(.*)"$/, async (path) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ throw error
+ }
+ })
+ then(/^o status de resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^a resposta contém id "(.*)" e nome "(.*)"$/, (id, name) => {
+ expect(response.data).toEqual(
+ expect.objectContaining({
+ _id: id,
+ name: name
+ })
+ )})
+ })
+})
\ No newline at end of file
diff --git a/backend/tests/controllers/reviews/review1.steps.ts b/backend/tests/controllers/reviews/review1.steps.ts
new file mode 100644
index 0000000000..cf7e958ce4
--- /dev/null
+++ b/backend/tests/controllers/reviews/review1.steps.ts
@@ -0,0 +1,54 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Review = require("../../../models/Review")
+
+const feature = loadFeature('tests/features/reviews/review1.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Visualização de um Review', ({ given, when, then, and }) => {
+ given(/^O restaurante de ID "(.*)" contém um review feito pelo usuário de ID "(.*)" de título "(.*)", texto "(.*)" e nota "(.*)"$/, async (idrest, iduser, title, text, rating) => {
+
+ const review = await Review.findOne({user: iduser, restaurant: idrest, title: title, text: text, rating: rating})
+
+ expect(review).toEqual(
+ expect.objectContaining({
+ title: title
+ })
+ )
+
+ })
+ when(/^é feita uma requisição GET para "(.*)"$/, async (path) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^O status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^Deve ser retornado um JSON com o review "(.*)"$/, (title) => {
+ expect(response.data).toEqual(
+ expect.objectContaining({
+ title: title
+ })
+ )})
+ });
+});
diff --git a/backend/tests/controllers/reviews/review2.steps.ts b/backend/tests/controllers/reviews/review2.steps.ts
new file mode 100644
index 0000000000..8dd6b98007
--- /dev/null
+++ b/backend/tests/controllers/reviews/review2.steps.ts
@@ -0,0 +1,58 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Review = require("../../../models/Review")
+
+const feature = loadFeature('tests/features/reviews/review2.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Criar Review', ({ given, when, then, and }) => {
+ given(/^Não existe review feito pelo usuário de ID "(.*)" no restaurante de ID "(.*)"$/, async (idrest, iduser) => {
+
+ const review = await Review.findOne({user: iduser, restaurant: idrest})
+
+ expect(review).toBe(null)
+
+ })
+ when(/^Uma requisição POST é feita para "(.*)" com título "(.*)", texto "(.*)" e nota "(.*)"$/, async (path, title, text, rating) => {
+ const pathSplit = path.split("/");
+ const iduser = pathSplit[3]
+ const idrest = pathSplit[2]
+
+ try {
+ response = await axios.post(`${SERVER_URL}${path}`, {title: title, text: text, rating: rating, user: iduser, restaurant: idrest})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^O status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^O review "(.*)" associada ao usuário "(.*)" e restaurante "(.*)" está no banco de dados$/, async (title, iduser, idrest) => {
+ const review = await Review.findOne({title: title, user: iduser, restaurant: idrest})
+
+ expect(review).toEqual(
+ expect.objectContaining({
+ title: title,
+ })
+ )})
+
+
+ });
+});
diff --git a/backend/tests/controllers/reviews/review3.steps.ts b/backend/tests/controllers/reviews/review3.steps.ts
new file mode 100644
index 0000000000..7d544a0f7a
--- /dev/null
+++ b/backend/tests/controllers/reviews/review3.steps.ts
@@ -0,0 +1,81 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Review = require("../../../models/Review")
+
+const feature = loadFeature('tests/features/reviews/review3.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Obter lista de reviews de um restaurante', ({ given, when, then, and }) => {
+ given(/^O restaurante de id "(.*)" contém três reviews "(.*)", "(.*)" e "(.*)"$/, async (idrest, title1, title2, title3) => {
+
+ const rev1 = await Review.findOne({restaurant: idrest, title: title1})
+ const rev2 = await Review.findOne({restaurant: idrest, title: title2})
+ const rev3 = await Review.findOne({restaurant: idrest, title: title3})
+
+ expect(rev1).toEqual(
+ expect.objectContaining({
+ title: title1
+ })
+ )
+
+ expect(rev2).toEqual(
+ expect.objectContaining({
+ title: title2
+ })
+ )
+
+ expect(rev3).toEqual(
+ expect.objectContaining({
+ title: title3
+ })
+ )
+
+ })
+ when(/^é feita uma requisição GET para "(.*)"$/, async (path) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^O status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^Deve ser retornado um JSON com os três reviews "(.*)", "(.*)" e "(.*)"$/, async (title1, title2, title3) => {
+ expect(response.data[0]).toEqual(
+ expect.objectContaining({
+ title: title1
+ })
+ )
+
+ expect(response.data[1]).toEqual(
+ expect.objectContaining({
+ title: title2
+ })
+ )
+
+ expect(response.data[2]).toEqual(
+ expect.objectContaining({
+ title: title3
+ })
+ )
+ });
+ });
+});
diff --git a/backend/tests/controllers/reviews/review4.steps.ts b/backend/tests/controllers/reviews/review4.steps.ts
new file mode 100644
index 0000000000..f169e60076
--- /dev/null
+++ b/backend/tests/controllers/reviews/review4.steps.ts
@@ -0,0 +1,60 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Review = require("../../../models/Review")
+
+const feature = loadFeature('tests/features/reviews/review4.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test('Edição de Review', ({ given, when, then, and }) => {
+ given(/^O restaurante de ID "(.*)" contém um review feito pelo usuário de ID "(.*)" e título "(.*)"$/, async (idrest, iduser, title) => {
+
+ const review = await Review.findOne({user: iduser, restaurant: idrest, title: title})
+
+ expect(review).toEqual(
+ expect.objectContaining({
+ title: title,
+ })
+ )
+
+
+ })
+ when(/^é feita uma requisição PUT para "(.*)" alterando o título para "(.*)"$/, async (path, title) => {
+ const pathSplit = path.split("/");
+ const iduser = pathSplit[3]
+ const idrest = pathSplit[2]
+ const review = await Review.findOne({user: iduser, restaurant: idrest})
+
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {title: title, text: review.text, rating: review.rating, user: iduser, restaurant: idrest})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^O status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^Deve ser retornado um JSON contendo o review "(.*)"$/, async (title) => {
+ expect(response.data).toEqual(
+ expect.objectContaining({
+ title: title
+ })
+ )})
+ });
+});
diff --git a/backend/tests/controllers/reviews/review5.steps.ts b/backend/tests/controllers/reviews/review5.steps.ts
new file mode 100644
index 0000000000..c76d3b1238
--- /dev/null
+++ b/backend/tests/controllers/reviews/review5.steps.ts
@@ -0,0 +1,53 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+
+const Review = require("../../../models/Review")
+
+const feature = loadFeature('tests/features/reviews/review5.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+ let response: AxiosResponse
+
+ test('Remoção de Review', ({ given, when, then, and }) => {
+ given(/^O restaurante de ID "(.*)" contém um review feito pelo usuário de ID "(.*)" e título "(.*)"$/, async (idrest, iduser, title) => {
+
+ const review = await Review.findOne({user: iduser, restaurant: idrest, title: title})
+
+ expect(review).toEqual(
+ expect.objectContaining({
+ title: title,
+ })
+ )
+
+
+ })
+ when(/^é feita uma requisição DELETE para "(.*)"$/, async (path) => {
+ try {
+ response = await axios.delete(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^O status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^O review "(.*)" do restaurante de ID "(.*)" e usuário de ID "(.*)" não deve constar no banco de dados$/, async (title, idrest, iduser) => {
+ const review = await Review.findOne({user: iduser, restaurant: idrest, title: title})
+
+ expect(review).toEqual(null)
+ });
+ });
+});
diff --git a/backend/tests/controllers/searches/searches1.steps.ts b/backend/tests/controllers/searches/searches1.steps.ts
new file mode 100644
index 0000000000..64e0f05f51
--- /dev/null
+++ b/backend/tests/controllers/searches/searches1.steps.ts
@@ -0,0 +1,60 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+const Restaurant = require("../../../models/Restaurant")
+
+const feature = loadFeature('tests/features/searches/searches1.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+export async function connectDBForTesting() {
+ try {
+ const dbUri = "mongodb://localhost:27017";
+ const dbName = "test";
+ await mongoose.connect(dbUri, {
+ dbName,
+ autoCreate: true,
+ });
+ } catch (error) {
+ console.log("DB connect error");
+ }
+ }
+
+ export async function disconnectDBForTesting() {
+ try {
+ await mongoose.connection.close();
+ } catch (error) {
+ console.log("DB disconnect error");
+ }
+ }
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test("Busca por restaurante", ({ given, when, then, and }) => {
+ given(/^existe um restaurante cadastrado com nome "(.*)" e outro com nome "(.*)"$/, async (substring) => {
+ await Restaurant.create({ name: `Restaurante ${substring} Teste` });
+ })
+ when(/^é feita uma requisição GET para "(.*)" com nome "(.*)"$/, async (path, name) => {
+ try {
+ response = await axios.get(`${SERVER_URL}${path}`, { params: { name } });
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status da resposta deve ser "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^a resposta é "(.*)"$/, (ans) => {
+ expect(response.data).toEqual(ans)})
+ })
+})
diff --git a/backend/tests/controllers/user/user1.steps.ts b/backend/tests/controllers/user/user1.steps.ts
new file mode 100644
index 0000000000..8e67e1c9a3
--- /dev/null
+++ b/backend/tests/controllers/user/user1.steps.ts
@@ -0,0 +1,59 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/user/user1.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test("Editar nome de perfil por ID", ({ given, when, then, and }) => {
+ given(/^existe um usuário cadastrado com ID "(.*)" e nome "(.*)"$/, async (iduser, nameuser) => {
+
+ const user = await User.findById(iduser)
+
+ expect(String(nameuser)).toBe(user.name)
+ if(user.name !== nameuser){
+ console.log("Nomes não equivalentes")
+ return
+ }
+
+ if (!user) {
+ console.log("Usuário não encontrado")
+ return
+ }
+
+ })
+ when(/^uma requisição PUT foi enviada para "(.*)" alterando o nome para "(.*)"$/, async (path, newnameuser) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {name: newnameuser})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status da resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^existe um usuário cadastrado com ID "(.*)" e nome "(.*)" no banco de dados$/, async (iduser, newnameuser) => {
+ const user = await User.findById(iduser);
+ expect(user).toEqual(
+ expect.objectContaining({
+ name: newnameuser,
+ })
+ )})
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/user/user2.steps.ts b/backend/tests/controllers/user/user2.steps.ts
new file mode 100644
index 0000000000..c7ae9d540a
--- /dev/null
+++ b/backend/tests/controllers/user/user2.steps.ts
@@ -0,0 +1,49 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+const User = require("../../../models/User")
+
+const feature = loadFeature('tests/features/user/user2.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test("Deletar usuário por ID", ({ given, when, then, and }) => {
+ given(/^existe um usuário cadastrado com ID "(.*)"$/, async (iduser) => {
+
+ const user = await User.findById(iduser)
+
+ if (!user) {
+ console.log("Usuário não encontrado")
+ return
+ }
+
+ })
+ when(/^uma requisição DELETE foi enviada para "(.*)"$/, async (path) => {
+ try {
+ response = await axios.delete(`${SERVER_URL}${path}`)
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status da resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^o ID "(.*)" não existirá no banco de dados$/, async (iduser) => {
+ const user = await User.findById(iduser);
+ expect(user).toEqual(null)})
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/user/user3.steps.ts b/backend/tests/controllers/user/user3.steps.ts
new file mode 100644
index 0000000000..cf1da5c72b
--- /dev/null
+++ b/backend/tests/controllers/user/user3.steps.ts
@@ -0,0 +1,69 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+import { PassThrough } from "stream";
+const User = require("../../../models/User")
+const bcrypt = require('bcrypt');
+
+const feature = loadFeature('tests/features/user/user3.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test("Editar senha de perfil com sucesso", ({ given, when, then, and }) => {
+ given(/^existe um usuário cadastrado com ID "(.*)" e senha "(.*)"$/, async (iduser, senha) => {
+
+ const user = await User.findById(iduser)
+ const cpr = await bcrypt.compare(senha, user.password)
+ let verify = false
+
+ if(!cpr){
+ console.log("Senha não equivalente")
+ return
+ } else{
+ verify = true
+ }
+
+ expect(verify).toBe(true)
+
+ if (!user) {
+ console.log("Usuário não encontrado")
+ return
+ }
+
+ })
+ when(/^uma requisição PUT foi enviada para "(.*)" com a senha atual "(.*)" e nova senha "(.*)"$/, async (path, curSenha, newSenha) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {password: curSenha, newPassword: newSenha})
+ } catch (error) {
+ console.error('Error during HTTP request:', error)
+ return
+ }
+ })
+ then(/^o status da resposta é "(.*)"$/, (status) => {
+ expect(String(response.status)).toBe(status)
+ })
+ and(/^o ID "(.*)" estará associado com a senha "(.*)" no banco de dados$/, async(iduser, senha) => {
+ const user = await User.findById(iduser)
+ const cpr = bcrypt.compare(senha, user.password)
+ let verify = false
+ if(cpr){
+ verify = true
+ }
+ expect(verify).toBe(true)
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/controllers/user/user4.steps.ts b/backend/tests/controllers/user/user4.steps.ts
new file mode 100644
index 0000000000..9cbd929609
--- /dev/null
+++ b/backend/tests/controllers/user/user4.steps.ts
@@ -0,0 +1,67 @@
+import { loadFeature, defineFeature } from "jest-cucumber"
+import axios, { AxiosResponse } from 'axios'
+const mongoose = require('mongoose')
+require("dotenv").config()
+import {connectDBForTesting, disconnectDBForTesting} from '../common'
+import { PassThrough } from "stream";
+const User = require("../../../models/User")
+const bcrypt = require('bcrypt');
+
+const feature = loadFeature('tests/features/user/user4.feature');
+
+const SERVER_URL = 'http://localhost:3001'
+
+defineFeature(feature, test => {
+
+ beforeAll(async () => {
+ await connectDBForTesting();
+ });
+ afterAll(async () => {
+ await disconnectDBForTesting();
+ });
+
+ let response: AxiosResponse
+
+ test("Editar senha de perfil com falha", ({ given, when, then, and }) => {
+ given(/^existe um usuário cadastrado com ID "(.*)" e senha "(.*)"$/, async (iduser, senha) => {
+
+ const user = await User.findById(iduser)
+ const cpr = await bcrypt.compare(senha, user.password)
+ let verify = false
+
+ if(!cpr){
+ console.log("Senha não equivalente")
+ return
+ } else{
+ verify = true
+ }
+
+ expect(verify).toBe(true)
+
+ if (!user) {
+ console.log("Usuário não encontrado")
+ return
+ }
+
+ })
+ when(/^uma requisição PUT foi enviada para "(.*)" com a senha atual "(.*)" e nova senha "(.*)"$/, async (path, curSenha, newSenha) => {
+ try {
+ response = await axios.put(`${SERVER_URL}${path}`, {newPassword: newSenha, password: curSenha})
+ } catch (error) {
+ return
+ }
+ })
+ then(/^o status da resposta é "(.*)"$/, (status) => {
+ expect(response).toBe(undefined)
+ })
+ and(/^o ID "(.*)" estará associado com a senha "(.*)" no banco de dados$/, async(iduser, senha) => {
+ const user = await User.findById(iduser)
+ const cpr = bcrypt.compare(senha, user.password)
+ let verify = false
+ if(cpr){
+ verify = true
+ }
+ expect(verify).toBe(true)
+ })
+ });
+});
\ No newline at end of file
diff --git a/backend/tests/database/lists.json b/backend/tests/database/lists.json
new file mode 100644
index 0000000000..e702cca582
--- /dev/null
+++ b/backend/tests/database/lists.json
@@ -0,0 +1,18 @@
+[{
+ "_id": {
+ "$oid": "65d58ca2c3082d4949f7cd06"
+ },
+ "name": "Restaurantes que amei",
+ "author": "Joaozinho",
+ "restaurants": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d58d6cc3082d4949f7cd0a"
+ },
+ "name": "restaurantes que to amando",
+ "author": "Joaozinho",
+ "restaurants": [],
+ "__v": 0
+}]
\ No newline at end of file
diff --git a/backend/tests/database/ratings.json b/backend/tests/database/ratings.json
new file mode 100644
index 0000000000..b28b5904cf
--- /dev/null
+++ b/backend/tests/database/ratings.json
@@ -0,0 +1,117 @@
+[{
+ "_id": {
+ "$oid": "65d2cd21620894960053d342"
+ },
+ "user": {
+ "$oid": "15d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 5,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cd9c620894960053d348"
+ },
+ "user": {
+ "$oid": "02d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 4,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cdc2620894960053d34d"
+ },
+ "user": {
+ "$oid": "03d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 3,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2ce51620894960053d353"
+ },
+ "user": {
+ "$oid": "25d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 1,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cec3620894960053d358"
+ },
+ "user": {
+ "$oid": "05d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 2,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cf41620894960053d360"
+ },
+ "user": {
+ "$oid": "15d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cee7620894960053d35c"
+ },
+ "rating": 5,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2d039620894960053d36d"
+ },
+ "user": {
+ "$oid": "25d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "rating": 1,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2d132620894960053d372"
+ },
+ "user": {
+ "$oid": "99d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "rating": 5,
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2d5dee914878d310a4915"
+ },
+ "user": {
+ "$oid": "00d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 1,
+ "__v": 0
+}]
\ No newline at end of file
diff --git a/backend/tests/database/restaurants.json b/backend/tests/database/restaurants.json
new file mode 100644
index 0000000000..05509e7d66
--- /dev/null
+++ b/backend/tests/database/restaurants.json
@@ -0,0 +1,178 @@
+[{
+ "_id": {
+ "$oid": "65c41aa2453ca1b7700a197c"
+ },
+ "name": "Marco Zero Pizza",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Boa Viagem",
+ "street": "João Lisboa",
+ "number": "300"
+ },
+ "typeOfFood": "Pizza",
+ "timestamp": "1707350579567",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65c568993aacefa6417aa0ed"
+ },
+ "name": "Tailandia's Food",
+ "address": {
+ "city": "São Paulo",
+ "neighborhood": "Bela Vista",
+ "street": "Avenida Paulista",
+ "number": "1223"
+ },
+ "typeOfFood": "Tailandesa",
+ "site": "rangodonamaria.com",
+ "timestamp": "1707436100347",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65cfc1777717da2f3845a961"
+ },
+ "name": "Italia's Food",
+ "address": {
+ "city": "São Paulo",
+ "neighborhood": "Ibirapuera",
+ "street": "Rua legal",
+ "number": "1200"
+ },
+ "typeOfFood": "Italiana",
+ "site": "rangoitaliano.com",
+ "timestamp": "1708114294204",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65cfeacc31d3a13d8be463c5"
+ },
+ "name": "Best Pizza",
+ "address": {
+ "city": "São Paulo",
+ "neighborhood": "Ibirapuera",
+ "street": "Rua legal",
+ "number": "1200"
+ },
+ "typeOfFood": "Italiana",
+ "site": "rangoitaliano.com",
+ "timestamp": "1708124090210",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d11e187f6b62a1b71494ef"
+ },
+ "name": "Comida Boa",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Boa Viagem",
+ "street": "João Lisboa",
+ "number": "300"
+ },
+ "typeOfFood": "Pizza",
+ "timestamp": "1708203503526",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2dcb28e72f63fd167a3ca"
+ },
+ "name": "Rango da Dona Maria",
+ "address": {
+ "city": "São Paulo",
+ "neighborhood": "Bela Vista",
+ "street": "Avenida Paulista",
+ "number": "1223"
+ },
+ "typeOfFood": "Regional",
+ "timestamp": "1708317489914",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65c413e6c75a35528c1fff83"
+ },
+ "name": "Sei lá Sushi",
+ "address": {
+ "city": "São Paulo",
+ "neighborhood": "Ibirapuera",
+ "street": "Rua legal",
+ "number": "1200"
+ },
+ "typeOfFood": "Italiana",
+ "site": "rangoitaliano.com",
+ "timestamp": "1708124090210",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "name": "Casa dos Doces",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Casa Amarela",
+ "street": "Estrada do Arraial",
+ "number": "3692"
+ },
+ "typeOfFood": "Doceria",
+ "timestamp": "1708299338409",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cee7620894960053d35c"
+ },
+ "name": "PV Sushi",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Várzea",
+ "street": "Polidoro",
+ "number": "121"
+ },
+ "typeOfFood": "Sushi",
+ "timestamp": "1708313886936",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "name": "Casa dos Doces 2",
+ "address": {
+ "city": "Recife 2",
+ "neighborhood": "Casa Amarela 2",
+ "street": "Estrada do Arraial 2",
+ "number": "3692"
+ },
+ "typeOfFood": "Doceria",
+ "timestamp": "1708313886936",
+ "profileImage": "None",
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d58ca2c3082d4949f7cd06"
+ },
+ "name": "Restaurante topper",
+ "address": {
+ "city": "Recife 2",
+ "neighborhood": "Casa Amarela 2",
+ "street": "Estrada do Arraial 2",
+ "number": "3692"
+ },
+ "typeOfFood": "Doceria",
+ "timestamp": "1708313886936",
+ "profileImage": "None",
+ "__v": 0
+}]
diff --git a/backend/tests/database/reviews.json b/backend/tests/database/reviews.json
new file mode 100644
index 0000000000..42e708fc84
--- /dev/null
+++ b/backend/tests/database/reviews.json
@@ -0,0 +1,147 @@
+[{
+ "_id": {
+ "$oid": "65d2cd21620894960053d341"
+ },
+ "title": "Coxinha Boa",
+ "user": {
+ "$oid": "15d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 5,
+ "text": "Boa Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:38:09.966Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cd9c620894960053d347"
+ },
+ "title": "Coxinha ok",
+ "user": {
+ "$oid": "02d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 4,
+ "text": "Ok Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:40:12.844Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cdc2620894960053d34c"
+ },
+ "title": "Coxinha meh",
+ "user": {
+ "$oid": "03d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d29514713ed7cc6fcf3635"
+ },
+ "rating": 3,
+ "text": "meh Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:40:50.574Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2cf41620894960053d35f"
+ },
+ "title": "Ótimo Sushi",
+ "user": {
+ "$oid": "15d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cee7620894960053d35c"
+ },
+ "rating": 5,
+ "text": "Sushi ótimo",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:47:13.544Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2d039620894960053d36c"
+ },
+ "title": "Coxinha Ruim",
+ "user": {
+ "$oid": "25d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "rating": 1,
+ "text": "Ruim Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:51:21.101Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2d132620894960053d371"
+ },
+ "title": "Coxinha Bem Boa",
+ "user": {
+ "$oid": "99d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "rating": 5,
+ "text": "Bem Boa Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T03:55:30.488Z"
+ },
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d2fd87a99b48e9c55d479c"
+ },
+ "title": "Coxinha eh",
+ "user": {
+ "$oid": "05d29514713ed7cc6fcf3635"
+ },
+ "restaurant": {
+ "$oid": "65d2cfb1620894960053d364"
+ },
+ "rating": 2,
+ "text": "Eh Coxinha",
+ "likes": 0,
+ "dislikes": 0,
+ "views": 0,
+ "created": {
+ "$date": "2024-02-19T07:04:39.826Z"
+ },
+ "__v": 0
+}]
\ No newline at end of file
diff --git a/backend/tests/database/users.json b/backend/tests/database/users.json
new file mode 100644
index 0000000000..078c334192
--- /dev/null
+++ b/backend/tests/database/users.json
@@ -0,0 +1,175 @@
+[{
+ "_id": {
+ "$oid": "65d51e36c3b06ec45cdd2ac8"
+ },
+ "name": "Guilhme",
+ "email": "guilherme@gmail.com",
+ "password": "$2b$10$cMVZLjltQcVAn1cr7JkAFuZvK5UCuzzw/3/GHEzijvYb9TflwJgS2",
+ "following": [],
+ "followers": [
+ "65d58c5ec3082d4949f7cd03"
+ ],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d51f9ac3b06ec45cdd2acb"
+ },
+ "name": "Leticia",
+ "email": "mleticiamn@gmail.com",
+ "password": "$2b$10$fJGC9hUzNGmgwQJSzlnLdu69vie2NN6g1J4GX.rL72dP16Ok5GENG",
+ "following": [],
+ "followers": [
+ "65d58c5ec3082d4949f7cd03",
+ "65e9b508bc7d45d6d4e029c0"
+ ],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d51fdec3b06ec45cdd2ace"
+ },
+ "name": "Joao",
+ "email": "joaozinho123@gmail.com",
+ "password": "$2b$10$Bq89YC2Q4wSxruJGcFiKSO7lfJ2Qd0k7vTHLkWpn.Mope7k/tJVZO",
+ "following": [],
+ "followers": [],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d5218ac3b06ec45cdd2ad1"
+ },
+ "name": "Pedro",
+ "email": "pedrinhofeliz123@gmail.com",
+ "password": "$2b$10$7PYyfSKRlx5wjQ1m3IwIg.zunThOTJYcpK6tD0A8VL/9MIeqLFDf2",
+ "following": [],
+ "followers": [],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d1ebd4077a9668192c4feb"
+ },
+ "followers": [],
+ "following": [],
+ "name": "unknow",
+ "reviews": []
+},
+{
+ "_id": {
+ "$oid": "65d1ebf4077a9668192c4fee"
+ },
+ "name": "Carla Marinho",
+ "followers": [],
+ "following": [],
+ "reviews": []
+},
+{
+ "_id": {
+ "$oid": "65d1ebe2077a9668192c4fec"
+ },
+ "name": "João Bezerra",
+ "followers": [],
+ "following": [
+ "65d52ef89eb929aa5b3532a4"
+ ],
+ "reviews": []
+},
+{
+ "_id": {
+ "$oid": "65d1ebec077a9668192c4fed"
+ },
+ "name": "Caio Lins",
+ "followers": [],
+ "following": [],
+ "reviews": []
+},
+{
+ "_id": {
+ "$oid": "65d1eb21077a9668192c4fe8"
+ },
+ "name": "Guilherme Maranhão",
+ "followers": [],
+ "following": [
+ "65d58c5ec3082d4949f7cd03"
+ ],
+ "reviews": []
+},
+{
+ "_id": {
+ "$oid": "65d52ef89eb929aa5b3532a4"
+ },
+ "name": "Roberto",
+ "email": "Roberto@gmail.com",
+ "password": "$2b$10$BPxM.anuj1aj3PZzxjpnYeAba.C63r08cFvgfqN7U8U0pOVfG2dZi",
+ "following": [
+ "65e9b508bc7d45d6d4e029c0"
+ ],
+ "followers": [
+ "65d58c5ec3082d4949f7cd03",
+ "65d1ebe2077a9668192c4fec"
+ ],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65d58c5ec3082d4949f7cd03"
+ },
+ "name": "Joaozinho",
+ "email": "ESS@gmail.com",
+ "password": "$2b$10$BPxM.anuj1aj3PZzxjpnYeAba.C63r08cFvgfqN7U8U0pOVfG2dZi",
+ "following": [
+ "65d51e36c3b06ec45cdd2ac8",
+ "65d52ef89eb929aa5b3532a4",
+ "65d51f9ac3b06ec45cdd2acb"
+ ],
+ "followers": [
+ "65d1eb21077a9668192c4fe8"
+ ],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65e9b508bc7d45d6d4e029c0"
+ },
+ "name": "Joaninha",
+ "email": "tijb@mail.com",
+ "password": "$2b$10$XuSH19QvEhScyRAjtuev/uO4l8x05UdwuISNAM197AqB12R0K8FNK",
+ "following": [
+ "65d51f9ac3b06ec45cdd2acb"
+ ],
+ "followers": [
+ "65d52ef89eb929aa5b3532a4"
+ ],
+ "reviews": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65f11386ac06cba390ece79a"
+ },
+ "name": "omena",
+ "email": "omena@gmail.com",
+ "password": "$2b$10$5805LFj63fNCsfvs1G8PW.nsNqHPTXHcFvWTghsn8dyx3BnuVo1eW",
+ "following": [],
+ "followers": [],
+ "__v": 0
+},
+{
+ "_id": {
+ "$oid": "65f141644af0af2c019c2e1a"
+ },
+ "name": "taylors",
+ "email": "taylors@gmail.com",
+ "password": "$2b$10$2X3S6qS43.6Adv3JYGonYel4pK9bOvn0wXyHCpP5/edT3VCijNbsy",
+ "following": [],
+ "followers": [],
+ "__v": 0
+}]
\ No newline at end of file
diff --git a/backend/tests/features/followers/listFollowers.feature b/backend/tests/features/followers/listFollowers.feature
new file mode 100644
index 0000000000..b274c34bb9
--- /dev/null
+++ b/backend/tests/features/followers/listFollowers.feature
@@ -0,0 +1,7 @@
+Feature: followers
+
+Scenario: Pegar lista de seguidores
+ Given o usuário com id "65d1eb21077a9668192c4fe8" está armazenado no sistema com a lista de seguidores "65d1ebd4077a9668192c4feb", "65d1ebf4077a9668192c4fec", "65d1ebf4077a9668192c4fee"
+ When fizer uma requisição GET com rota "/users/followers/65d1eb21077a9668192c4fe8"
+ Then o status do sistema é 200
+ And o sistema retorna um JSON com a lista "65d1ebd4077a9668192c4feb", "65d1ebf4077a9668192c4fec", "65d1ebf4077a9668192c4fee"
\ No newline at end of file
diff --git a/backend/tests/features/followers/listFollowersUnit.feature b/backend/tests/features/followers/listFollowersUnit.feature
new file mode 100644
index 0000000000..0a75b3979a
--- /dev/null
+++ b/backend/tests/features/followers/listFollowersUnit.feature
@@ -0,0 +1,6 @@
+Feature: followers
+
+Scenario: Pegar lista de seguidores - teste unitário
+ Given o usuário com id "65d1eb21077a9668192c4fe8" está armazenado no sistema com a lista de seguidores "65d1ebd4077a9668192c4feb", "65d1ebf4077a9668192c4fee", "65d1ebf4077a9668192c4fee"
+ When fizer a busca pela lista de seguidores do usuário "65d1eb21077a9668192c4fe8"
+ And o sistema retorna um JSON com a lista "65d1ebd4077a9668192c4feb", "65d1ebf4077a9668192c4fee", "65d1ebf4077a9668192c4fee"
\ No newline at end of file
diff --git a/backend/tests/features/followers/listFollowing.feature b/backend/tests/features/followers/listFollowing.feature
new file mode 100644
index 0000000000..3578a943a7
--- /dev/null
+++ b/backend/tests/features/followers/listFollowing.feature
@@ -0,0 +1,7 @@
+Feature: following
+
+Scenario: Pegar lista de usuários que segue
+ Given o usuário com o id "65d1ebec077a9668192c4fed" está armazenado no sistema com a lista "65d1eb21077a9668192c4fe8", "65d1ebe2077a9668192c4fec", "65d1ebf4077a9668192c4fee" de usuários que segue
+ When fizer uma requisição GET com rota "/users/following/65d1ebec077a9668192c4fed"
+ Then o status do sistema é 200
+ And o sistema retorna um JSON com a lista "65d1eb21077a9668192c4fe8", "65d1ebe2077a9668192c4fec", "65d1ebf4077a9668192c4fee"
\ No newline at end of file
diff --git a/backend/tests/features/followers/seguirUsuario.feature b/backend/tests/features/followers/seguirUsuario.feature
new file mode 100644
index 0000000000..3961b80644
--- /dev/null
+++ b/backend/tests/features/followers/seguirUsuario.feature
@@ -0,0 +1,10 @@
+Feature: followers
+
+Scenario: Seguir um usuário
+ Given o usuário com id "65d1ebd4077a9668192c4feb" está armazenado no sistema com a lista de usuários que segue vazia
+ And o usuário com o id "65d1eb21077a9668192c4fe8" está armazenado no sistema com a lista de seguidores vazia e com e-mail "almocin.ess@gmail.com"
+ When fizer uma requisição PUT com rota "/users/follow/65d1eb21077a9668192c4fe8" e o body contendo o id "65d1ebd4077a9668192c4feb"
+ Then o status do sistema é 200
+ And retorna um JSON com os dados do usuário com o id "65d1eb21077a9668192c4fe8" que tem a lista de seguidores "65d1ebd4077a9668192c4feb"
+ And com os dados do usuário com id "65d1ebd4077a9668192c4feb" tem a lista de usuários que segue "65d1eb21077a9668192c4fe8"
+ And a mensagem enviada para o e-mail cadastrado do usuário seguido tem status "success"
\ No newline at end of file
diff --git a/backend/tests/features/followers/unfollow.feature b/backend/tests/features/followers/unfollow.feature
new file mode 100644
index 0000000000..2126e1396c
--- /dev/null
+++ b/backend/tests/features/followers/unfollow.feature
@@ -0,0 +1,9 @@
+Feature: Followers
+
+Scenario: Deixar de seguir um usuário
+ Given o usuário com id "65d1ebe2077a9668192c4fec" está armazenado no sistema com a lista de usuários que segue "65d1ebec077a9668192c4fed"
+ And o usuário com o id "65d1ebec077a9668192c4fed" está armazenado no sistema com a lista de seguidores "65d1ebe2077a9668192c4fec"
+ When fizer uma requisição PUT com rota "/users/unfollow/65d1ebec077a9668192c4fed" e o body contendo o id "65d1ebe2077a9668192c4fec"
+ Then o status do sistema é 200
+ And retorna um JSON contendo o id "65d1ebec077a9668192c4fed" e a lista de seguidores vazia
+ And contendo o id "65d1ebe2077a9668192c4fec" e a lista de usuários que segue vazia
\ No newline at end of file
diff --git a/backend/tests/features/lists/list1.feature b/backend/tests/features/lists/list1.feature
new file mode 100644
index 0000000000..24b9053b49
--- /dev/null
+++ b/backend/tests/features/lists/list1.feature
@@ -0,0 +1,8 @@
+Feature: Lists
+
+ Scenario: Criar lista com sucesso
+ Given um usuário de nome "Leticia" está logado com ID "65d51f9ac3b06ec45cdd2acb"
+ When uma requisição POST é enviada para "/lists/create/65d51f9ac3b06ec45cdd2acb" com o nome "Favoritos"
+ Then o status de resposta é "200"
+ And um JSON com o nome do autor "Leticia" e nome da lista "Favoritos" é retornado
+ And a lista do usuário com ID "65d51f9ac3b06ec45cdd2acb" com nome "Favoritos" pode ser encontrada no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/lists/list2.feature b/backend/tests/features/lists/list2.feature
new file mode 100644
index 0000000000..8ebe36b701
--- /dev/null
+++ b/backend/tests/features/lists/list2.feature
@@ -0,0 +1,7 @@
+Feature: Lists
+
+ Scenario: Excluir lista por ID
+ Given existe uma lista de restaurantes com ID "65d58ca2c3082d4949f7cd06" com autor de ID "65d58c5ec3082d4949f7cd03" e nome "Restaurantes que amei"
+ When uma requisição DELETE é enviada para "/lists/delete/65d58ca2c3082d4949f7cd06"
+ Then o status de resposta é "200"
+ And a lista com ID "65d58ca2c3082d4949f7cd06" não pode ser encontrada no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/lists/list3.feature b/backend/tests/features/lists/list3.feature
new file mode 100644
index 0000000000..bba9eb6813
--- /dev/null
+++ b/backend/tests/features/lists/list3.feature
@@ -0,0 +1,9 @@
+Feature: Lists
+
+ Scenario: Tentar criar uma lista sem nome
+ Given o usuário de ID "65d58c5ec3082d4949f7cd03" está logado
+ When uma requisição POST é enviada para "/lists/create/65d58c5ec3082d4949f7cd03" com nenhum nome e descrição "sem nome"
+ Then o status de resposta é "400"
+ And a lista sem nome não pode ser encontrada no banco de dados com autor de ID "65d58c5ec3082d4949f7cd03"
+
+
\ No newline at end of file
diff --git a/backend/tests/features/lists/list4.feature b/backend/tests/features/lists/list4.feature
new file mode 100644
index 0000000000..7f8705015d
--- /dev/null
+++ b/backend/tests/features/lists/list4.feature
@@ -0,0 +1,7 @@
+Feature: Lists
+
+ Scenario: Editar o nome de uma lista
+ Given existe uma lista de restaurantes com ID "65d58d6cc3082d4949f7cd0a" e nome "restaurantes que to amando" com autor de nome "Joaozinho"
+ When uma requisição PUT é enviada para "/lists/edit/65d58d6cc3082d4949f7cd0a" com o novo nome "ex favoritos"
+ Then o status de resposta é "200"
+ And a lista com ID "65d58d6cc3082d4949f7cd0a" é atualizada com nome "ex favoritos" no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/login/login1.feature b/backend/tests/features/login/login1.feature
new file mode 100644
index 0000000000..9f4e5b3948
--- /dev/null
+++ b/backend/tests/features/login/login1.feature
@@ -0,0 +1,10 @@
+Feature:Login
+
+ Scenario:criar usuário
+ Given não existe um usuário cadastrado com o nome "jose", email "jose@gmail.com" e senha "Joao1245&"
+ When uma requisição POST foi enviada para "/users/signup" com o nome "jose", email "jose@gmail.com" e senha "Joao1245&"
+ Then o status de resposta é "200"
+ And um usuário é cadastrado com nome "jose", email "jose@gmail.com" e senha "Joao1245&"
+
+
+
diff --git a/backend/tests/features/login/login2.feature b/backend/tests/features/login/login2.feature
new file mode 100644
index 0000000000..d1f29a7926
--- /dev/null
+++ b/backend/tests/features/login/login2.feature
@@ -0,0 +1,7 @@
+Feature:Login
+
+ Scenario:logar o usuário
+ Given existe um usuário com nome "Roberto", email "Roberto@gmail.com" e senha "SenhaComplicada#"
+ When uma requisição POST foi enviada para "/users/signin " com o email "Roberto@gmail.com" e senha "SenhaComplicada#"
+ Then o status de resposta é "200"
+ And um usuário é logado com nome "Roberto" e email "Roberto@gmail.com"
diff --git a/backend/tests/features/login/login3.feature b/backend/tests/features/login/login3.feature
new file mode 100644
index 0000000000..ec925a6bcf
--- /dev/null
+++ b/backend/tests/features/login/login3.feature
@@ -0,0 +1,7 @@
+Feature:Login
+
+ Scenario:A senha não contém caracteres especiais e letras maiusculas
+ Given não existe um usuário cadastrado com o nome "tiago", email "tiago@gmail.com" e senha "senhasimples"
+ When uma requisição POST foi enviada para "/users/signup" com nome "tiago", email "tiago@gmail.com" e senha "senhasimples"
+ Then o status de resposta é "404 Not Found"
+ And é retornado o aviso "A senha deve conter no mínimo 1 caracter maiúsculo, 1 caracter minúsculo, 1 simbolo especial e tamanho de pelo menos 8."
diff --git a/backend/tests/features/login/login4.feature b/backend/tests/features/login/login4.feature
new file mode 100644
index 0000000000..b74db3bc7c
--- /dev/null
+++ b/backend/tests/features/login/login4.feature
@@ -0,0 +1,7 @@
+Feature:Login
+
+ Scenario:A senha para login está errada
+ Given existe um usuário cadastrado com nome "Joao", email "joao@gmail.com" e senha "Joao1245&"
+ When uma requisição POST foi enviada para "/user/signin" com o email "joao@gmail.com" e senha "Joao456&"
+ Then o status da resposta é "404 Not Found"
+ And é retornado o aviso "Invalid password"
diff --git a/backend/tests/features/login/login5.feature b/backend/tests/features/login/login5.feature
new file mode 100644
index 0000000000..9734dda678
--- /dev/null
+++ b/backend/tests/features/login/login5.feature
@@ -0,0 +1,7 @@
+Feature:Login
+
+ Scenario:O email não está no formato adequado
+ Given não existe um usuário cadastrado com o nome "Pedro", email "Pedro" e senha "Pedro1245&"
+ When uma requisição POST foi enviada para "/user/signup" com o nome "Pedro", email "Pedro" e senha "Pedro456&"
+ Then o status de resposta é "200"
+ And é retornado o aviso "Invalid email"
diff --git a/backend/tests/features/restaurant/restaurant_create.feature b/backend/tests/features/restaurant/restaurant_create.feature
new file mode 100644
index 0000000000..ed9e25824f
--- /dev/null
+++ b/backend/tests/features/restaurant/restaurant_create.feature
@@ -0,0 +1,8 @@
+Feature: Restaurant
+
+ Scenario: cadastrar restaurante novo
+ Given não existe um restaurante cadastrado com nome "Marcelinho Doces" e endereço "Rua dos Reitores, 220 - Várzea, Recife"
+ When uma requisição POST foi enviada para "/restaurants/create" com nome "Marcelinho Doces", endereço "Rua dos Reitores, 220 - Várzea, Recife" e tipo de comida "Doces"
+ Then o status de resposta é "200"
+ And existe um restaurante cadastrado com nome "Marcelinho Doces", endereço "Rua dos Reitores, 220 - Várzea, Recife" e tipo de comida "Doces"
+
diff --git a/backend/tests/features/restaurant/restaurant_create_error.feature b/backend/tests/features/restaurant/restaurant_create_error.feature
new file mode 100644
index 0000000000..33732000a2
--- /dev/null
+++ b/backend/tests/features/restaurant/restaurant_create_error.feature
@@ -0,0 +1,8 @@
+ Feature: Restaurant
+
+ Scenario: erro ao cadastrar restaurante existente
+
+ Given existe um restaurante cadastrado com nome "Rango da Dona Maria" e endereço "Avenida Paulista, 1223 - Bela Vista, São Paulo"
+ When uma requisição POST foi enviada para "/restaurants/create" com nome "Rango da Dona Maria", endereço "Avenida Paulista, 1223 - Bela Vista, São Paulo" e tipo de comida "Regional"
+ Then o status de resposta é "400"
+ And a resposta é "Restaurante já cadastrado"
diff --git a/backend/tests/features/restaurant/restaurant_delete.feature b/backend/tests/features/restaurant/restaurant_delete.feature
new file mode 100644
index 0000000000..41a1b0c5e4
--- /dev/null
+++ b/backend/tests/features/restaurant/restaurant_delete.feature
@@ -0,0 +1,7 @@
+Feature: Restaurant
+
+ Scenario: Deletar restaurante por ID
+ Given existe um restaurante cadastrado com id "65c413e6c75a35528c1fff83" e nome "Sei lá Sushi"
+ When uma requisição DELETE foi enviada para "/restaurants/delete/65c413e6c75a35528c1fff83"
+ Then o status de resposta é "200"
+ And não existe um restaurante cadastrado com id "65c413e6c75a35528c1fff83" e nome "Sei lá Sushi"
diff --git a/backend/tests/features/restaurant/restaurant_edit.feature b/backend/tests/features/restaurant/restaurant_edit.feature
new file mode 100644
index 0000000000..23e02ece97
--- /dev/null
+++ b/backend/tests/features/restaurant/restaurant_edit.feature
@@ -0,0 +1,7 @@
+Feature: Restaurant
+
+ Scenario: Editar restaurante por ID
+ Given existe um restaurante cadastrado com id "65cfeacc31d3a13d8be463c5" e nome "Best Pizza"
+ When uma requisição PUT foi enviada para "/restaurants/edit/65cfeacc31d3a13d8be463c5" com nome "Worst Pizza"
+ Then o status de resposta é "200"
+ And existe um restaurante cadastrado com id "65cfeacc31d3a13d8be463c5" e nome "Worst Pizza"
\ No newline at end of file
diff --git a/backend/tests/features/restaurant/restaurant_get.feature b/backend/tests/features/restaurant/restaurant_get.feature
new file mode 100644
index 0000000000..e186175338
--- /dev/null
+++ b/backend/tests/features/restaurant/restaurant_get.feature
@@ -0,0 +1,7 @@
+Feature: Restaurant
+
+ Scenario: Obter restaurante por ID
+ Given existe um restaurante cadastrado com id "65c41aa2453ca1b7700a197c" e nome "Marco Zero Pizza"
+ When uma requisição GET foi enviada para "/restaurants/65c41aa2453ca1b7700a197c"
+ Then o status de resposta é "200"
+ And a resposta contém id "65c41aa2453ca1b7700a197c" e nome "Marco Zero Pizza"
diff --git a/backend/tests/features/reviews/review1.feature b/backend/tests/features/reviews/review1.feature
new file mode 100644
index 0000000000..e0fe51fb0c
--- /dev/null
+++ b/backend/tests/features/reviews/review1.feature
@@ -0,0 +1,7 @@
+Feature: Reviews
+
+ Scenario: Visualização de um Review
+ Given O restaurante de ID "65d29514713ed7cc6fcf3635" contém um review feito pelo usuário de ID "15d29514713ed7cc6fcf3635" de título "Coxinha Boa", texto "Boa Coxinha" e nota "5"
+ When é feita uma requisição GET para "/reviews/65d29514713ed7cc6fcf3635/15d29514713ed7cc6fcf3635"
+ Then O status da resposta deve ser "200"
+ And Deve ser retornado um JSON com o review "Coxinha Boa"
\ No newline at end of file
diff --git a/backend/tests/features/reviews/review2.feature b/backend/tests/features/reviews/review2.feature
new file mode 100644
index 0000000000..915c25e8b9
--- /dev/null
+++ b/backend/tests/features/reviews/review2.feature
@@ -0,0 +1,7 @@
+Feature: Reviews
+
+Scenario: Criar Review
+ Given Não existe review feito pelo usuário de ID "00d29514713ed7cc6fcf3635" no restaurante de ID "65d29514713ed7cc6fcf3635"
+ When Uma requisição POST é feita para "/reviews/65d29514713ed7cc6fcf3635/00d29514713ed7cc6fcf3635/create" com título "Coxinha Bem Ruim", texto "Bem Ruim Coxinha" e nota "1"
+ Then O status da resposta deve ser "200"
+ And O review "Coxinha Bem Ruim" associada ao usuário "00d29514713ed7cc6fcf3635" e restaurante "65d29514713ed7cc6fcf3635" está no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/reviews/review3.feature b/backend/tests/features/reviews/review3.feature
new file mode 100644
index 0000000000..0f686b8235
--- /dev/null
+++ b/backend/tests/features/reviews/review3.feature
@@ -0,0 +1,7 @@
+Feature: Reviews
+
+Scenario: Obter lista de reviews de um restaurante
+ Given O restaurante de id "65d29514713ed7cc6fcf3635" contém três reviews "Coxinha Boa", "Coxinha ok" e "Coxinha meh"
+ When é feita uma requisição GET para "/reviews/65d29514713ed7cc6fcf3635"
+ Then O status da resposta deve ser "200"
+ And Deve ser retornado um JSON com os três reviews "Coxinha Boa", "Coxinha ok" e "Coxinha meh"
\ No newline at end of file
diff --git a/backend/tests/features/reviews/review4.feature b/backend/tests/features/reviews/review4.feature
new file mode 100644
index 0000000000..1634f2cbda
--- /dev/null
+++ b/backend/tests/features/reviews/review4.feature
@@ -0,0 +1,7 @@
+Feature: Reviews
+
+Scenario: Edição de Review
+ Given O restaurante de ID "65d2cfb1620894960053d364" contém um review feito pelo usuário de ID "25d29514713ed7cc6fcf3635" e título "Coxinha Ruim"
+ When é feita uma requisição PUT para "/reviews/65d2cfb1620894960053d364/25d29514713ed7cc6fcf3635/edit" alterando o título para "Coxinha Mais ou Menos"
+ Then O status da resposta deve ser "200"
+ And Deve ser retornado um JSON contendo o review "Coxinha Mais ou Menos"
\ No newline at end of file
diff --git a/backend/tests/features/reviews/review5.feature b/backend/tests/features/reviews/review5.feature
new file mode 100644
index 0000000000..ce78460078
--- /dev/null
+++ b/backend/tests/features/reviews/review5.feature
@@ -0,0 +1,7 @@
+Feature: Reviews
+
+Scenario: Remoção de Review
+ Given O restaurante de ID "65d2cfb1620894960053d364" contém um review feito pelo usuário de ID "05d29514713ed7cc6fcf3635" e título "Coxinha eh"
+ When é feita uma requisição DELETE para "/reviews/65d2cfb1620894960053d364/05d29514713ed7cc6fcf3635/delete"
+ Then O status da resposta deve ser "200"
+ And O review "Coxinha eh" do restaurante de ID "65d2cfb1620894960053d364" e usuário de ID "05d29514713ed7cc6fcf3635" não deve constar no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/searches/searches1.feature b/backend/tests/features/searches/searches1.feature
new file mode 100644
index 0000000000..7482b7c6c0
--- /dev/null
+++ b/backend/tests/features/searches/searches1.feature
@@ -0,0 +1,7 @@
+Feature: Searches
+
+ Scenario: Busca por restaurante
+ Given existe um restaurante cadastrado com nome "Casa dos Doces" e outro com nome "Pizza Hut"
+ When é feita uma requisição GET para "/searches/search_restaurant" com nome "Doces"
+ Then o status da resposta deve ser "200"
+ And a resposta é "Casa dos Doces"
diff --git a/backend/tests/features/user/user1.feature b/backend/tests/features/user/user1.feature
new file mode 100644
index 0000000000..16a31e25d4
--- /dev/null
+++ b/backend/tests/features/user/user1.feature
@@ -0,0 +1,7 @@
+Feature: User
+
+Scenario: Editar nome de perfil por ID
+ Given existe um usuário cadastrado com ID "65d51e36c3b06ec45cdd2ac8" e nome "Guilhme"
+ When uma requisição PUT foi enviada para "/users/edit/65d51e36c3b06ec45cdd2ac8" alterando o nome para "Guilherme"
+ Then o status da resposta é "200"
+ And existe um usuário cadastrado com ID "65d51e36c3b06ec45cdd2ac8" e nome "Guilherme" no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/user/user2.feature b/backend/tests/features/user/user2.feature
new file mode 100644
index 0000000000..915385a0fc
--- /dev/null
+++ b/backend/tests/features/user/user2.feature
@@ -0,0 +1,7 @@
+Feature: User
+
+ Scenario: Deletar usuário por ID
+ Given existe um usuário cadastrado com ID "65d51f9ac3b06ec45cdd2acb"
+ When uma requisição DELETE foi enviada para "/users/delete/65d51f9ac3b06ec45cdd2acb"
+ Then o status da resposta é "200"
+ And o ID "65d51f9ac3b06ec45cdd2acb" não existirá no banco de dados
\ No newline at end of file
diff --git a/backend/tests/features/user/user3.feature b/backend/tests/features/user/user3.feature
new file mode 100644
index 0000000000..da10cb1e53
--- /dev/null
+++ b/backend/tests/features/user/user3.feature
@@ -0,0 +1,7 @@
+Feature: User
+
+ Scenario: Editar senha de perfil com sucesso
+ Given existe um usuário cadastrado com ID "65d51fdec3b06ec45cdd2ace" e senha "adk##44e2ASx$"
+ When uma requisição PUT foi enviada para "/users/editPass/65d51fdec3b06ec45cdd2ace" com a senha atual "adk##44e2ASx$" e nova senha "avcd!44e2ASx$"
+ Then o status da resposta é "200"
+ And o ID "65d51fdec3b06ec45cdd2ace" estará associado com a senha "avcd!44e2ASx$" no banco de dados
diff --git a/backend/tests/features/user/user4.feature b/backend/tests/features/user/user4.feature
new file mode 100644
index 0000000000..77acfa1e44
--- /dev/null
+++ b/backend/tests/features/user/user4.feature
@@ -0,0 +1,7 @@
+Feature: User
+
+ Scenario: Editar senha de perfil com falha
+ Given existe um usuário cadastrado com ID "65d5218ac3b06ec45cdd2ad1" e senha "abbddef!@S"
+ When uma requisição PUT foi enviada para "/users/editPass/65d5218ac3b06ec45cdd2ad1" com a senha atual "abbddef!@S" e nova senha "12345"
+ Then o status da resposta é "400"
+ And o ID "65d5218ac3b06ec45cdd2ad1" estará associado com a senha "abbddef!@S" no banco de dados
\ No newline at end of file
diff --git a/backend/tests/request.rest b/backend/tests/request.rest
new file mode 100644
index 0000000000..ccf75588a9
--- /dev/null
+++ b/backend/tests/request.rest
@@ -0,0 +1,61 @@
+GET http://localhost:3001/restaurants
+
+###
+
+POST http://localhost:3001/restaurants
+Content-Type: application/json
+
+{
+ "name": "Restaurante Genérico"
+}
+
+###
+
+POST http://localhost:3001/restaurants
+Content-Type: application/json
+
+{
+ "name": "Sei lá Sushi",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Várzea",
+ "street": "Polidoro",
+ "number": "121"
+ },
+ "typeOfFood": "Sushi"
+}
+
+###
+
+POST http://localhost:3001/restaurants
+Content-Type: application/json
+
+{
+ "name": "Marco Zero Pizza",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Boa Viagem",
+ "street": "João Lisboa",
+ "number": "300"
+ },
+ "typeOfFood": "Pizza"
+}
+###
+
+DELETE http://localhost:3001/restaurant/delete/65c417a0de877005385a8d75
+
+###
+
+PUT http://localhost:3001/restaurant/edit/65c417a0de877005385a8d75
+Content-Type: application/json
+
+{
+ "name": "Sei lá Sushi",
+ "address": {
+ "city": "Recife",
+ "neighborhood": "Várzea",
+ "street": "Polidoro",
+ "number": "220"
+ },
+ "typeOfFood": "Sushi"
+}
diff --git a/backend/tsconfig.json b/backend/tsconfig.json
new file mode 100644
index 0000000000..e42dcb80b6
--- /dev/null
+++ b/backend/tsconfig.json
@@ -0,0 +1,109 @@
+{
+ "compilerOptions": {
+ /* Visit https://aka.ms/tsconfig to read more about this file */
+
+ /* Projects */
+ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
+ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
+ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
+ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
+ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
+
+ /* Language and Environment */
+ "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+ // "jsx": "preserve", /* Specify what JSX code is generated. */
+ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
+ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
+ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
+ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
+ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
+
+ /* Modules */
+ "module": "commonjs", /* Specify what module code is generated. */
+ // "rootDir": "./", /* Specify the root folder within your source files. */
+ // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
+ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
+ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
+ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
+ // "typeRoots": ["jest-cucumber"], /* Specify multiple folders that act like './node_modules/@types'. */
+ // "types": [], /* Specify type package names to be included without being referenced in a source file. */
+ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
+ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
+ // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+ // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
+ // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
+ // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+ // "resolveJsonModule": true, /* Enable importing .json files. */
+ // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
+ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
+
+ /* JavaScript Support */
+ //"allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
+ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+ /* Emit */
+ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+ // "declarationMap": true, /* Create sourcemaps for d.ts files. */
+ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
+ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
+ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
+ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+ // "outDir": "./", /* Specify an output folder for all emitted files. */
+ // "removeComments": true, /* Disable emitting comments. */
+ // "noEmit": true, /* Disable emitting files from a compilation. */
+ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
+ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
+ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
+ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
+ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+ // "newLine": "crlf", /* Set the newline character for emitting files. */
+ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
+ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
+ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
+ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
+ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
+
+ /* Interop Constraints */
+ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
+ // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
+ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
+
+ /* Type Checking */
+ "strict": true, /* Enable all strict type-checking options. */
+ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
+ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
+ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
+ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
+ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
+ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
+ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
+ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
+ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
+ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
+ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
+ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
+ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
+ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
+ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
+
+ /* Completeness */
+ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
+ "skipLibCheck": true /* Skip type checking all .d.ts files. */
+ }
+}
diff --git a/backend/uploads/1709944458295.jpg b/backend/uploads/1709944458295.jpg
new file mode 100644
index 0000000000..46aa593b9d
Binary files /dev/null and b/backend/uploads/1709944458295.jpg differ
diff --git a/backend/uploads/1709955898780.jpg b/backend/uploads/1709955898780.jpg
new file mode 100644
index 0000000000..ed289e04a4
Binary files /dev/null and b/backend/uploads/1709955898780.jpg differ
diff --git a/backend/uploads/1709992334056.jpg b/backend/uploads/1709992334056.jpg
new file mode 100644
index 0000000000..1e0a71c575
Binary files /dev/null and b/backend/uploads/1709992334056.jpg differ
diff --git a/backend/uploads/1710015836887.jpg b/backend/uploads/1710015836887.jpg
new file mode 100644
index 0000000000..b3bd3a0bdd
Binary files /dev/null and b/backend/uploads/1710015836887.jpg differ
diff --git a/backend/uploads/1710017078418.jpg b/backend/uploads/1710017078418.jpg
new file mode 100644
index 0000000000..8b98cd5df6
Binary files /dev/null and b/backend/uploads/1710017078418.jpg differ
diff --git a/backend/uploads/1710038094304.png b/backend/uploads/1710038094304.png
new file mode 100644
index 0000000000..da8ff5a342
Binary files /dev/null and b/backend/uploads/1710038094304.png differ
diff --git a/backend/uploads/1710083490129.png b/backend/uploads/1710083490129.png
new file mode 100644
index 0000000000..49df6414e2
Binary files /dev/null and b/backend/uploads/1710083490129.png differ
diff --git a/backend/uploads/1710096744050.png b/backend/uploads/1710096744050.png
new file mode 100644
index 0000000000..38b7c6ffcf
Binary files /dev/null and b/backend/uploads/1710096744050.png differ
diff --git a/backend/uploads/1710133232935.png b/backend/uploads/1710133232935.png
new file mode 100644
index 0000000000..38b7c6ffcf
Binary files /dev/null and b/backend/uploads/1710133232935.png differ
diff --git a/backend/uploads/1710133252315.jpg b/backend/uploads/1710133252315.jpg
new file mode 100644
index 0000000000..6934a3a2ce
Binary files /dev/null and b/backend/uploads/1710133252315.jpg differ
diff --git a/backend/uploads/1710283381890.png b/backend/uploads/1710283381890.png
new file mode 100644
index 0000000000..649fed0fb8
Binary files /dev/null and b/backend/uploads/1710283381890.png differ
diff --git a/backend/uploads/1710285480827.jpg b/backend/uploads/1710285480827.jpg
new file mode 100644
index 0000000000..6460c6193e
Binary files /dev/null and b/backend/uploads/1710285480827.jpg differ
diff --git a/backend/uploads/1710285498704.png b/backend/uploads/1710285498704.png
new file mode 100644
index 0000000000..49df6414e2
Binary files /dev/null and b/backend/uploads/1710285498704.png differ
diff --git a/backend/uploads/1710285932148.jpg b/backend/uploads/1710285932148.jpg
new file mode 100644
index 0000000000..ed289e04a4
Binary files /dev/null and b/backend/uploads/1710285932148.jpg differ
diff --git a/backend/utils/sendEmail.js b/backend/utils/sendEmail.js
new file mode 100644
index 0000000000..9fe770a942
--- /dev/null
+++ b/backend/utils/sendEmail.js
@@ -0,0 +1,39 @@
+const nodemailer = require("nodemailer")
+
+const sendEmail = async (subject, message, send_to) => {
+ const transporter = nodemailer.createTransport({
+ host: "smtp-mail.outlook.com",
+ port: "587",
+ auth: {
+ user: "almocin.ess@outlook.com",
+ pass: "almocin.email"
+ },
+ tls: {
+ rejectUnauthorized: false
+ }
+ })
+
+ const options = {
+ from: "almocin.ess@outlook.com",
+ to: send_to,
+ replyTo: "almocin.ess@outlook.com",
+ subject: subject,
+ html: message
+ }
+
+ let error = false
+
+ transporter.sendMail(options, function(err, info){
+ if (err){
+ error = true
+ }
+ })
+
+ if(error){
+ return "error"
+ } else {
+ return "success"
+ }
+}
+
+module.exports = sendEmail
\ No newline at end of file
diff --git a/config/cli.py b/config/cli.py
deleted file mode 100644
index d68f80cae7..0000000000
--- a/config/cli.py
+++ /dev/null
@@ -1,146 +0,0 @@
-import select
-import sys
-import time
-import inquirer
-import platform
-import os
-
-from constants import MAP, FRAMEWORKS
-
-def create_clickable_link(url, text):
- return f'\033]8;;{url}\033\\{text}\033]8;;\033\\'
-
-def clear():
- if platform.system() == 'Windows':
- os.system('cls') or None
- else:
- os.system('clear') or None
-
-def wait_and_clear(s=1):
- wait(s)
- clear()
- print()
-
-def wait(s=1):
- time.sleep(s)
-
-def typing_effect(message, delay=0.02):
- index = 0
- if platform.system() == 'Windows':
- import msvcrt
-
- for char in message:
- sys.stdout.write(char)
- sys.stdout.flush()
- if msvcrt.kbhit() and msvcrt.getch() == b'\r':
- sys.stdout.write('\033[F')
- sys.stdout.write(' 🗂 🛠 ')
- sys.stdout.write(message)
- sys.stdout.flush()
- return
- time.sleep(delay)
- index += 1
- else:
- for char in message:
- print(char, end='', flush=True)
- if sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
- if sys.stdin.readline().strip() == '':
- print('\033[F', end='', flush=True)
- print(' 🗂 🛠 ', end='')
- print(message)
- return
- time.sleep(delay)
- index += 1
- print()
-
-def add_subtree(framework, key, folder):
- url = f'{MAP[key]}Software-Engineering-Assistantship/{MAP[framework]}-ess.git'
- os.system(f'git subtree add --prefix {folder} {url} main --squash')
-
-if __name__ == '__main__':
- clear()
- print('\n 🗂 🛠 ', end='')
-
- while(True):
- typing_effect('Olá! Seja bem vindo à CLI para criação do seu projeto base!')
-
- answers = inquirer.prompt(FRAMEWORKS)
-
- backend = answers['backend']
- frontend = answers['frontend']
- key = answers['key']
-
- typing_effect(f"""Você escolheu:
- -> Frontend: {frontend}
- -> Backend: {backend}
- -> Clone via: {key} """)
-
- answers = inquirer.prompt([
- inquirer.Confirm('are_you_sure', message='⚠️ Tem certeza?')
- ])
-
- if answers['are_you_sure']:
- link = create_clickable_link('https://www.atlassian.com/br/git/tutorials/git-subtree', 'clique aqui')
-
- typing_effect(' 🫡 Entendido!')
-
- wait_and_clear(4)
-
- typing_effect("O seu projeto será criado utilizando o conceito de 'Subtree' do Git. 🌳")
- typing_effect('Isso significa que ele será composto por uma cópia de dois repositórios, um frontend e um backend. 📂')
- typing_effect('Esses repositórios serão clonados para dentro do seu projeto, em pastas separadas.')
-
- wait(1)
-
- typing_effect('Mas ok, vamos lá! Chega de explicações... vou criar logo o seu projeto base! ⌛️ \n\n')
-
- wait(3)
-
- add_subtree(frontend, key, 'frontend')
- add_subtree(backend, key, 'backend')
-
- wait_and_clear(4)
-
- typing_effect("Parabéns! Seu projeto foi criado com sucesso! 🎉\nVocês já podem começar a trabalhar nele! 💻🚀")
-
- wait_and_clear(4)
-
- typing_effect("Os commits já foram feitos para vocês, então não precisam se preocupar com isso.\nSe quiserem, podem dar uma olhada! 👀")
- typing_effect("É só utilizar o comando 'git log' para ver o histórico de commits. 📜")
- typing_effect("Então, para publicar as novidades que acabaram de ser adicionadas no seu projeto, basta dar um 'git push'. 📤")
-
- wait(1)
-
- typing_effect("\nEnfim, se tiverem alguma dúvida, podem nos chamar! O time de monitoria está aqui para ajudar vocês! 🤓🤝")
- typing_effect("E se tiverem alguma sugestão de melhoria, podem nos dizer também. 🤩")
- typing_effect("Espero que o seu projeto seja um sucesso! 👌")
-
- wait(1)
-
- typing_effect("\nMas de agora em diante... que a força esteja com vocês! 🪐💪✨")
-
- wait(2)
-
- typing_effect(f"\n\nPara mais informações sobre as subtrees do Git, segure 'ctrl' e {link}! 📚 ")
- typing_effect("\nAté mais! 👋")
-
- wait(2)
-
- break
- else:
-
- answers = inquirer.prompt([
- inquirer.Confirm(
- 'exit',
- message=f'🚪 Deseja sair?')
- ])
-
- if answers['exit']:
- typing_effect('Saindo... Até mais! 👋')
- wait()
- exit()
-
- typing_effect('Reiniciando CLI... 🔄')
- wait(2)
- clear()
- continue
diff --git a/config/constants.py b/config/constants.py
deleted file mode 100644
index 59b185b7d7..0000000000
--- a/config/constants.py
+++ /dev/null
@@ -1,30 +0,0 @@
-import inquirer
-
-MAP = {
- 'React ⚛️': 'frontend-react',
- 'Vue.js 🔥': 'frontend-vue',
- 'Angular 🅰️': 'frontend-angular',
- 'Next.js 🇳': 'frontend-nextjs',
- 'NodeJS 🚀': 'backend-nodejs',
- 'FastAPI ⚡️': 'backend-fastAPI',
- 'HTTPS 🔒': 'https://github.com/',
- 'SSH 🔑': 'git@github.com:'
-}
-
-FRAMEWORKS = [
- inquirer.List(
- 'frontend',
- message='Escolha um Framework de Frontend para utilizar no seu projeto:',
- choices=['React ⚛️','Vue.js 🔥','Angular 🅰️', 'Next.js 🇳'],
- ),
- inquirer.List(
- 'backend',
- message='Escolha um Framework de Backend para utilizar no seu projeto:',
- choices=['NodeJS 🚀','FastAPI ⚡️'],
- ),
- inquirer.List(
- 'key',
- message='Deseja clonar os repositórios pelo GitHub via:',
- choices=['HTTPS 🔒','SSH 🔑'],
- ),
-]
diff --git a/features/analysis_search_content.feature b/features/analysis_search_content.feature
new file mode 100644
index 0000000000..3f3006383c
--- /dev/null
+++ b/features/analysis_search_content.feature
@@ -0,0 +1,24 @@
+Scenario: Descobrir um restaurante novo
+ Given o usuário “Laís” está na página inicial do sistema
+ When o usuário “Laís” visualiza um ponto gastronômico chamado “Casa dos Doces”
+ And clica no ícone de “Casa dos Doces”
+ Then os dados registrados de “Casa dos Doces” serão exibidos
+
+Scenario: Seção “Em alta” quando existem restaurantes cadastrados no sistema
+ Given o usuário “Laís” está na página inicial do sistema
+ When o usuário “Laís” visualiza a seção “Reviews em Alta”
+ Then a review “Coxinha fria” estará nessa seção
+ And ao clicar em “Coxinha fria”
+ Then o conteúdo em “Coxinha fria” será exibido
+
+Scenario: Seção “Mais vistos” quando existem restaurantes cadastrados no sistema
+ Given o usuário “Laís” está na página inicial do sistema
+ When o usuário “Laís” visualiza a seção “Mais vistos”
+ Then a review “O melhor bem-casado da cidade ” estará nessa seção
+ And ao clicar em “O melhor bem-casado da cidade”
+ Then o conteúdo em “Coxinha fria” será exibido
+
+Scenario: “Busca com filtro” quando existem restaurantes cadastrados no sistema
+ Given o usuário “Laís” está em qualquer página do sistema
+ When o usuário “Laís” seleciona o filtro “Confeitaria”
+ Then o resultado da pesquisa terá somente restaurantes com o filtro “Confeitaria”
diff --git a/features/edit.feature b/features/edit.feature
new file mode 100644
index 0000000000..b7bc945925
--- /dev/null
+++ b/features/edit.feature
@@ -0,0 +1,124 @@
+Scenario: Edição de nome do perfil
+
+ Given estou na tela de “Edição do perfil” e logado como "gmsmr@cin.ufpe.br"
+ When eu preencho o campo “Nome de usuário” com “Guilherme”
+ And eu salvo minha alteração
+ Then uma mensagem de confirmação aparece
+ And o meu nome estará alterado para “Guilherme”
+
+
+Scenario: Edição de foto de perfil
+
+ Given estou na tela de “Edição do perfil” e logado como "gmsmr@cin.ufpe.br"
+ When eu aperto o botão “Trocar Ícone”
+ And eu seleciono a foto “smiling.png”
+ And eu salvo minha alteração
+ Then uma mensagem de confirmação aparece
+ And a minha foto estará alterada para “smiling.png”
+
+
+Scenario: Edição de bio de perfil
+
+ Given estou na tela de “Edição do perfil” e logado como "gmsmr@cin.ufpe.br"
+ When eu preencho o campo “Bio" com “new person. stopped judging”
+ And eu salvo minha alteração
+ Then uma mensagem de confirmação aparece
+ And o minha bio estará alterada para “new person. stopped judging”
+
+
+Scenario: Edição de capa do perfil
+
+ Given estou na tela de “Edição do perfil” e logado como "gmsmr@cin.ufpe.br"
+ When eu aperto o botão “Trocar Capa”
+ And eu seleciono a foto “ratatouille.png”
+ And eu salvo minha alteração
+ Then uma mensagem de confirmação aparece
+ And a minha capa estará alterada para “ratatouille.png”
+
+
+Scenario: Edição de senha do perfil
+
+ Given estou na tela de “Trocar senha” logado como “gmsmr@cin.ufpe.br“ e minha senha é “!Abc1234”
+ When eu preencho o campo “Senha atual” com “!Abc1234”
+ And eu preencho o campo “Nova senha” com “qwertYy@123a”
+ And eu preencho o campo “Confirmar nova senha” com “qwertYy123a”
+ And salvo minha mudança
+ Then uma mensagem de confirmação aparece
+ And a minha senha estará alterada para “qwerty123a”
+
+
+Scenario: Remoção de perfil
+
+ Given estou na tela “Menu de Edição” e logado como "gmsmr@cin.ufpe.br" e minha senha é “qwerty123a”
+ And aperto o botão “Avançado”
+ And uma mensagem de alerta aparece
+ And confirmo o alerta
+ When eu preencho o campo “Digite sua senha” com “qwerty123a”
+ And eu preencho o campo “Confirme sua senha” com “qwerty123a”
+ And eu salvo minha alteração
+ Then uma mensagem de confirmação aparece
+ And minha conta estará removida
+
+
+Scenario: Edição de senha inválida do perfil
+
+ Given estou na tela de “Trocar senha” e logado como "gmsmr@cin.ufpe.br" e minha senha é “qwertYy@123a”
+ When eu preencho o campo “Senha atual” com “qwertYy@123a”
+ And eu preencho o campo “Nova senha” com “123456789”
+ And eu preencho o campo “Confirmar nova senha” com “123456789”
+ Then uma mensagem de falha aparece
+ And sou redirecionado para tela "Trocar senha"
+
+
+Scenario: Remoção de perfil com falha
+
+ Given estou na tela "Avançado" logado como "gmsmr@cin.ufpe.br" e minha senha é “qwertYy@123a”
+ And uma mensagem de alerta aparece
+ And confirmo o alerta
+ When eu preencho o campo "Digite sua senha" com "qwertYy@123a"
+ And eu preencho o campo "Confirme sua senha" com "qwertYy@123"
+ And confirmo minha decisão
+ Then uma mensagem de falha aparece
+ And sou redirecionado para tela "Avançado"
+
+## Cenários de Serviço
+
+Scenario: Obter usuário por ID
+
+ Given existe um usuário cadastrado com ID “1989” e nome “Guilherme”
+ When uma requisição “GET” foi enviada para /user/1989
+ Then o status de resposta é “200”
+ And a resposta contém ID “1989” e nome “Guilherme”
+
+Scenario: Editar nome de perfil por ID
+
+ Given existe um usuário cadastrado com ID “1259” e nome “Guilhme”
+ When uma requisição “PUT” foi enviada para “/user/edit/1259”
+ And o body da requisição contém o nome “Guilherme”
+ Then o status da resposta é “200”
+ And existe um usuário cadastrado com ID “1259” e nome “Guilherme”
+
+Scenario: Editar senha de perfil com sucesso
+
+ Given existe um usuário cadastrado com ID “1259” e senha “abcD12!3”
+ When uma requisição “PUT” foi enviada para “user/editPass/1259”
+ And o body da requisição contém a senha “dAac$123v”
+ Then o status da resposta é “200”
+ And a resposta é “Senha alterada com sucesso!”
+ And o ID “1259” estará associado com a senha “dAac$123v”
+
+Scenario: Editar senha de perfil com falha
+
+ Given existe um usuário cadastrado com ID “1259” e senha “abcD12!3”
+ When uma requisição “PUT” foi enviada para “user/editPass/1259”
+ And o body da requisição contém a senha “abcd123e”
+ Then o status da resposta é “404”
+ And a resposta é “A senha deve conter no mínimo 1 caractere maiúsculo, 1 caractere minúsculo, 1 simbolo especial e tamanho de pelo menos 8.”
+ And o ID “1259” estará associado com a senha “abcD12!3”
+
+Scenario: Deletar usuário por ID
+
+ Given existe um usuário cadastrado com ID “8192”
+ When uma requisição “DELETE” foi enviada para “user/delete/8192”
+ Then o status da resposta é “200”
+ And o ID “8192” não existirá no banco de dados
diff --git a/features/followers.feature b/features/followers.feature
new file mode 100644
index 0000000000..d36f940fb9
--- /dev/null
+++ b/features/followers.feature
@@ -0,0 +1,196 @@
+Feature: seguidores/lista de seguidores
+
+ As a usuário do site AlmoCIn
+ I want to seguir outros usuários
+ And visualizar os seguidores de qualquer usuário
+ So that eu ache outros usuários facilmente
+ And visualizar os reviews desses usuários
+ And achar outros usuários que seguem e são seguidos por esse usuário
+
+//Cenários GUI
+
+ Scenario: Visualização seguidores na página do usuário
+ Given eu estou cadastrado no sistema
+ And estou na página do meu perfil
+ And vejo que tenho "7 seguidores"
+ When eu seleciono “seguidores”
+ Then eu deveria ver a lista de todos os meus seguidores
+ And eu posso "Ver perfil" de cada seguidor
+ And posso "Seguir" seguidores que ainda não sigo
+ And posso "Deixar de seguir" usuários que já sigo
+
+ Scenario: Seguir usuário a partir da lista de seguidores
+ Given eu estou logado na conta "Guilherme Maranhão"
+ And estou na minha página de perfil
+ And vejo a lista com meus seguidores
+ When eu sigo "Amanda Napolitano"
+ Then eu deveria ver a mensagem de confirmação: "Você agora segue Amanda Napolitano. Enviamos uma notificação sobre esta ação."
+ And eu volto para minha lista de seguidores
+ And eu posso "Deixar de seguir" "Amanda Napolitano"
+
+ Scenario: Acessar o perfil de um usuário a partir da página de outro
+ Given estou logado como "Guilherme Maranhão"
+ And estou na minha página de perfil
+ And vejo uma lista com usuários que me seguem
+ When eu seleciono "Ver perfil" de "Amanda Napolitano"
+ Then eu estou na página de perfil de "Amanda Napolitano"
+
+ Scenario: Seguir usuário a partir da página de perfil
+ Given estou logado como "Guilherme Maranhão"
+ And estou na página de perfil de "Amanda Napolitano"
+ And eu não sigo "Amanda Napolitano"
+ And "Amanda Napolitano" tem "97 seguidores"
+ And eu posso "Seguir" "Amanda Napolitano"
+ When eu seleciono "Seguir"
+ And aparece a mensagem de confirmação: "Você agora segue Amanda Napolitano. Enviamos uma notificação sobre esta ação."
+ And eu fecho a mensagem
+ Then eu estou de volta na página de perfil de "Amanda Napolitano"
+ And "Amanda Napolitano" tem "98 seguidores"
+ And eu posso "Deixar de seguir"
+
+ Scenario: Visualização de seguidores de outro usuário
+ Given eu estou cadastrado no sistema
+ And estou na página do meu perfil
+ And vejo que tenho "15 seguindo"
+ When eu seleciono “seguindo”
+ Then eu deveria ver a lista de todos os usuários que eu sigo
+ And eu posso "Ver perfil" de cada usuário
+ And posso "Deixar de seguir" todos da lista
+
+ Scenario: Visualização "seguindo" na página do usuário
+ Given eu estou logado no sistema
+ And estou na página do meu perfil
+ And sigo 15 pessoas
+ When eu seleciono "seguindo"
+ Then eu deveria ver a lista com todos os usuários que sigo
+ And eu posso "Ver perfil" de todos os usuários
+ And eu posso "Deixar de seguir" todos os usuários da lista
+
+ Scenario: Deixar de seguir um usuário a partir da lista de seguindo
+ Given eu estou logado como "Guilherme Maranhão"
+ And estou na minha página de perfil
+ And vejo a lista com os usuários que eu sigo
+ And eu sigo "Ana Sofia"
+ When eu deixo de seguir "Ana Sofia"
+ And fecho a mensagem de confirmação: "Você deixou de seguir Ana Sofia."
+ Then eu volto a ver a lista de usuários que sigo
+ And "Ana Sofia" não está na lista
+
+ Scenario: Deixar de seguir um usuário a partir da página de perfil
+ Given estou logado como "Guilherme Maranhão"
+ And estou na página de perfil de "Amanda Napolitano"
+ And eu sigo "Amanda Napolitano"
+ And "Amanda Napolitano" tem "98 seguidores"
+ And eu posso "Deixar de seguir" "Amanda Napolitano"
+ When eu deixo de seguir "Amanda Napolitano"
+ And aparece a mensagem de confirmação: "Você deixou de seguir Amanda Napolitano."
+ And eu fecho a mensagem
+ Then eu estou de volta na página de perfil de "Amanda Napolitano"
+ And "Amanda Napolitano" tem "97 seguidores"
+ And eu posso "Seguir"
+
+ Scenario: Deixar de seguir um usuário a partir da lista de seguidores
+ Given eu estou logado na conta "Guilherme Maranhão"
+ And estou na minha página de perfil
+ And vejo a lista com meus seguidores
+ And "Amanda Napolitano" me segue
+ And "Amanda Napolitano" tem 98 seguidores
+ When deixo de seguir "Amanda Napolitano"
+ And fecho a mensagem de confirmação: "Você deixou de seguir Amanda Napolitano."
+ Then eu vejo minha lista de seguidores
+ And eu posso "Seguir" "Amanda Napolitano"
+ And "Amanda Napolitano" tem 97 seguidores
+
+ Scenario: Visualização de lista de seguidores vazia
+ Given estou cadastrado no sistema
+ And estou na minha página de perfil
+ And tenho "0 seguidores"
+ When eu seleciono "seguidores"
+ Then eu deveria ver uma mensagem "Você ainda não tem nenhum seguidor"
+
+ Scenario: Visualização de lista de seguindo vazia
+ Given estou cadastrado no sistema
+ And estou na minha página de perfil
+ And tenho "0 seguindo"
+ When eu seleciono "seguindor"
+ Then eu deveria ver uma mensagem "Você não segue nenhum usuário"
+
+//Cenários de Serviço
+
+ Scenario: Pegar lista de seguidores
+ Given o usuário com id "147" está armazenado no sistema
+ And tem como lista de seguidores "123, 456, 789"
+ When fizer uma requisição GET com rota "/users/followers/147"
+ Then o sistema retorna um JSON com a lista "123, 456, 789"
+ And o status do sistema é "200 OK"
+
+ Scenario: Pegar lista de seguidores vazia
+ Given o usuário com id "258" está armazenado no sistema com a lista de seguidores vazia
+ When fizer uma requisição GET com rota "/users/followers/258"
+ Then o status do sistema é "404 Not Found"
+ And retorna um JSON vazio
+
+ Scenario: Seguir um usuário
+ Given o usuário com id "258" está armazenado no sistema com a lista de usuários que segue vazia
+ And o usuário com o id "147" está armazenado no sistema com a lista de seguidores vazia e com e-mail "almocin.ess@gmail.com"
+ When fizer uma requisição PUT com rota "/users/follow/147" e o body contendo o id "258"
+ Then o status do sistema é "200 OK"
+ And retorna um JSON com os dados do usuário com o id "147" que tem a lista de seguidores "258"
+ And com os dados do usuário com id "258" tem a lista de usuários que segue "147"
+ And a mensagem enviada para o e-mail cadastrado do usuário seguido tem status "success"
+
+ Scenario: Pegar lista de usuários que segue
+ Given o usuário com o id "258" está armazenado no sistema
+ And tem a lista "123, 789, 147" de usuários que segue
+ When fizer uma requisição GET com rota "/users/following/258"
+ Then o sistema retorna um JSON com a lista "123, 789, 147"
+ And o status do sistema é "200 OK"
+
+ Scenario: Pegar lista de usuários que segue vazia
+ Given o usuário com o id "147" está armazenado no sistema com a lista de usuários que segue vazia
+ When fizer uma requisição GET com rota "/users/following/147"
+ Then o status do sistema é "404 Not Found"
+ And retorna um JSON vazio
+
+ Scenario: Seguir um usuário que já segue
+ Given o usuário com o id "258" está armazenado no sistema
+ And tem a lista "123, 789, 147" de usuários que segue
+ And o usuário com o id "147" está armazenado no sistema com a lista de seguidores "123, 456, 789, 258"
+ When fizer uma requisição PUT com rota "/users/follow/147"
+ And o body contendo o id "258"
+ Then o status do sistema é "409 Conflict"
+ And retorna um JSON contendo o id "258" e a lista de usuários que segue "123, 789, 147"
+ And contendo o id "147" e a lista de seguidores "123, 456, 789, 258"
+
+ Scenario: Deixar de seguir um usuário
+ Given o usuário com id "258" está armazenado no sistema com a lista de usuários que segue "147"
+ And o usuário com o id "147" está armazenado no sistema com a lista de seguidores "258"
+ When fizer uma requisição PUT com rota "/users/unfollow/147"
+ And o body contendo o id "258"
+ Then o status do sistema é "200 OK"
+ And retorna um JSON contendo o id "147" e a lista de seguidores vazia
+ And contendo o id "258" e a lista de usuários que segue vazia
+
+
+ Scenario: Deixar de seguir um usuário que não segue
+ Given o usuário com id "258" está armazenado no sistema com a lista de usuários que segue "123, 789"
+ And o usuário com o id "147" está armazenado no sistema com a lista de seguidores "123, 456, 789"
+ When fizer uma requisição PUT com rota "/users/unfollow/147"
+ And o body contendo o id "258"
+ Then o status do sistema é "409 Conflict"
+ And retorna um JSON contendo o id "147" e a lista de seguidores "123, 456, 789"
+ And contendo o id "258" e a lista de usuários que segue "123, 789"
+
+ Scenario: Deletar lista de seguidores e seguidos de um usuário na deleção do usuário
+ Given o usuário com id "123" está armazenado no sistema
+ And tem a lista de seguidores "258"
+ And tem a lista de usuários que segue "147"
+ And o usuário com o id "258" está armazenado no sistema
+ And tem a lista de usuários que segue "123, 789"
+ And o usuário com o id "147" está armazenado no sistema
+ And tem a lista de seguidores "123, 456, 789"
+ When fizer a requesição DELETE com rota "/users/delete/123"
+ Then o status do sistema é "200 OK"
+ And o usuário com o id "258" tem a lista de usuários que segue "789"
+ And o usuário com o id "147" tem a lista de seguidores "456, 789"
+ And o usuário com o id "123" não está armazenado no sistema
diff --git a/features/forum_comment.feature b/features/forum_comment.feature
new file mode 100644
index 0000000000..4ef0fed4c3
--- /dev/null
+++ b/features/forum_comment.feature
@@ -0,0 +1,20 @@
+Feature: Comentários de post de fórum
+
+ As a usuário comum
+ I want to comentar, editar e remover um post de fórum
+ So that eu possa interagir com outros usuários e participar de discussões
+
+ Scenario: Comentar em post
+ Given eu estou na página de um post de fórum
+ When eu seleciono a opção "Comentar"
+ And eu preencho o comentário com o conteúdo que desejo
+ And eu seleciono a opção de confirmação
+ Then eu vejo o comentário com o conteúdo desejado atribuído a mim na seção de comentários do post
+
+ Scenario: Edição de comentário
+ Given eu estou na página de um post de fórum
+ When eu seleciono a opção "Editar" em um comentário do meu usuário
+ And eu modifico o conteúdo do comentário
+ And eu seleciono a opção "Confirmar edição"
+ Then eu volto para a seção de comentários do post que estava
+ And eu consigo ver o comentário que fiz
diff --git a/features/forum_post.feature b/features/forum_post.feature
new file mode 100644
index 0000000000..34858881f7
--- /dev/null
+++ b/features/forum_post.feature
@@ -0,0 +1,79 @@
+Feature: Posts de fórum
+
+ As a usuário comum
+ I want to criar, editar e remover posts de fórum
+ So that eu possa compartilhar experiências, fazer perguntas ou discutir tópicos relevantes com a comunidade.
+
+ Scenario: Criar um novo post de fórum
+ Given eu estou na página de fórum
+ When eu seleciono a opção "Criar novo post"
+ And eu preencho o título com "Meu Post"
+ And eu preencho o contéudo com "Este é o conteúdo do meu post"
+ And eu seleciono a opção "Publicar"
+ Then eu recebo uma mensagem de confirmação de criação de post
+ And eu vejo o novo post com o título "Meu Post" e com o conteúdo "Este é o conteúdo do meu post" na página de fórum atribuído ao meu usuário
+
+ Scenario: Editar conteúdo de um post de fórum de um usuário
+ Given eu estou na página do post "Meu Post" do meu usuário
+ And o post possui o conteúdo "Este é o conteúdo do meu post"
+ When eu seleciono a opção "Editar conteúdo"
+ And eu modifico o conteúdo para o conteúdo "Este é o novo conteúdo do meu post"
+ And eu seleciono a opção "Confirmar edição"
+ Then eu recebo uma mensagem de confirmação de edição de post
+ And eu vejo "Este é o novo conteúdo do meu post" no conteúdo do post "Meu Post" do meu usuário
+
+ Scenario: Excluir um post de fórum do usuário
+ Given eu estou na página do post "Opções Mexicanas Pet Friendly" do meu usuário
+ When eu seleciono a opção "Excluir"
+ And eu confirmo a exclusão
+ Then eu recebo uma mensagem de confirmação de exclusão de post
+ And eu não vejo o post "Opções Mexicanas Pet Friendly" na página do conteúdo de fórum
+
+ Scenario: Comentar em post
+ Given eu estou na página de um post de fórum
+ When eu seleciono a opção "Comentar"
+ And eu preencho o comentário com o conteúdo que desejo
+ And eu seleciono a opção de confirmação
+ Then eu vejo o comentário com o conteúdo desejado atribuído a mim na seção de comentários do post
+
+ Scenario: Edição de comentário
+ Given eu estou na página de um post de fórum
+ When eu seleciono a opção "Editar" em um comentário do meu usuário
+ And eu modifico o conteúdo do comentário
+ And eu seleciono a opção "Confirmar edição"
+ Then eu volto para a seção de comentários do post que estava
+ And eu consigo ver o comentário que fiz
+
+
+ # Cenários de serviço
+
+ Scenario: Obter todos os posts de fórum
+ Given existem 2 posts de fórum
+ And o primeiro possui id "001", título "ABC", conteúdo "primeiro conteúdo" e autor usuário de id "123"
+ And o segundo possui id "002", título "DEF", conteúdo "segundo conteúdo" e autor usuário de id "456"
+ When uma requisição "GET" é feita para "/forum"
+ Then o status de resposta deve ser "200"
+ And o JSON da resposta deve ser uma lista de itens
+ And o post com id "001", título "ABC", conteúdo "primeiro conteúdo" e autor "123" deve estar na lista
+ And o post com id "002", título "DEF", conteúdo "segundo conteúdo" e autor "456" deve estar na lista
+
+ Scenario: Obter post de fórum por ID
+ Given existe um post de fórum com o id "1410"
+ And o post possui o título "Meu Post", conteúdo "Este é o conteúdo do meu post" e autor usuário de id "123"
+ When uma requisição "GET" é feita para "/forum/1410"
+ Then o status de resposta deve ser "200"
+ And o JSON de resposta deve conter id "1410", título "Meu Post", conteúdo "Este é o conteúdo do meu post" e autor "123"
+
+ Scenario: Editar post de fórum por ID
+ Given existe um post de fórum com o id "1410"
+ And o post possui o título "Meu Post", conteúdo "Este é o conteúdo do meu post" e autor usuário de id "123"
+ When uma requisição "PUT" é feita para "/forum/1410" com o JSON {"conteudo": "Este é o novo conteúdo do meu post"}
+ Then o status de resposta deve ser "200"
+ And o JSON de resposta deve conter id "1410", título "Meu Post", conteúdo "Este é o novo conteúdo do meu post" e autor "123"
+
+ Scenario: Excluir post de fórum por ID
+ Given existe um post de fórum com o id "1410"
+ And o post possui o título "Meu Post", conteúdo "Este é o conteúdo do meu post" e autor usuário de id "123"
+ When uma requisição "DELETE" é feita para "/forum/1410"
+ Then o status de resposta deve ser "200"
+ And o JSON de resposta deve conter uma mensagem de confirmação de exclusão
\ No newline at end of file
diff --git a/features/lists.feature b/features/lists.feature
new file mode 100644
index 0000000000..8b21edbfd2
--- /dev/null
+++ b/features/lists.feature
@@ -0,0 +1,107 @@
+Cenários GUI
+Scenario: Visualização de uma lista criada pelo usuário
+ Given eu estou na página de listas do meu perfil
+ When eu seleciono a opção "Minhas Listas"
+ And eu sou direcionado para uma tela contendo todas as minhas listas
+ And eu seleciono a lista "Favoritos"
+ Then sou direcionado para a página da lista "Favoritos"
+ And eu consigo visualizar todos os restaurantes da lista "Favoritos"
+
+Scenario: Visualização de uma lista curtida pelo usuário
+ Given eu estou na página de listas do meu perfil
+ When eu seleciono a opção "Listas Curtidas"
+ And eu sou direcionado para uma tela contendo todas as minhas listas curtidas
+ And eu seleciono a lista "Curtida123"
+ Then sou direcionado para a página da lista "Favoritos"
+ And eu consigo visualizar todos os restaurantes da "Curtida123"
+
+Scenario: Criar uma lista com sucesso
+ Given eu estou na página de listas do meu perfil
+ When eu seleciono a opção "Criar Lista"
+ And eu preencho o espaço de "Nome" com "NomeDaLista"
+ Then "NomeDaLista" aparecerá na página de listas
+
+Scenario: Criar uma lista sem sucesso
+ Given eu estou na página de listas do meu perfil
+ When eu seleciono a opção "Criar Lista"
+ And eu não preencho o espaço de "Nome" com "NomeDaLista"
+ And a mensagem "Insira o nome da lista" aparecer
+ And eu seleciono a opção "OK"
+ Then eu estou de volta na página de criação de lista
+
+Scenario: Excluir uma lista
+ Given eu estou na página de listas "Não gostei"
+ When eu clico em "Opções"
+ And eu seleciono "Excluir"
+ Then eu não posso mais visualizar a lista "Não gostei" na página de listas
+
+Scenario: Editar uma lista
+ Given eu estou na página de listas "Não gostei"
+ When eu clico em "Opções"
+ And eu seleciono "Editar"
+ Then eu vou para a página de edição da lista "Não gostei"
+
+Scenario: Compartilhar uma lista
+ Given eu estou na página de listas "Não gostei"
+ When eu clico em "opções"
+ And eu seleciono "Compartilhar"
+ Then eu gero um link único associado à lista "Não gostei"
+
+Scenario: Adição de restaurantes nas listas
+ Given eu estou logado no meu perfil
+ When seleciono o restaurante "Restaurante123"
+ And eu sou direcionado para a página contendo as informações sobre "Restaurante123"
+ And eu seleciono a opção "Adicionar"
+ And eu seleciono a lista "Favoritos"
+ Then o restaurante "Restaurante123" será adicionado na lista "Favoritos"
+ And eu poderei visualizar o restaurante "Restaurante123" na lista "Favoritos"
+
+Scenario: Remoção de restaurantes nas listas
+ Given eu estou na página da lista "Favoritos"
+ When eu seleciono o restaurante "Restaurante123" dessa lista
+ And clico na opção "Remover"
+ Then eu não posso mais visualizar o restaurante "Restaurante123" na lista "Favoritos"
+
+
+Cenários de Serviço
+Scenario: Criar lista
+ Given um usuário está logado com ID "123"
+ When uma requisição "POST" é enviada para "/lists" com os seguintes dados:
+ """
+ {
+ "name": "Favoritos",
+ }
+ """
+ Then o status de resposta é "200"
+ And a resposta contém um ID de lista gerado
+ And a lista é criada com nome "Favoritos"
+
+Scenario: Editar lista
+ Given existe uma lista de restaurantes com ID "456" e nome "Minha Lista"
+ And um usuário logado com ID "123" é o autor dessa lista
+ When uma requisição "PUT" é enviada para "/lists/edit/456" com os seguintes dados:
+ """
+ {
+ "name": "Minha Lista Atualizada",
+ }
+ """
+ Then o status de resposta é "200"
+ And a lista com ID "456" é atualizada com nome "Minha Lista Atualizada"
+
+Scenario: Excluir lista
+ Given existe uma lista de restaurantes com ID "789" e nome "Minha Lista Excluída"
+ And um usuário logado com ID "123" é o autor dessa lista
+ When uma requisição "DELETE" é enviada para "/lists/delete/789"
+ Then o status de resposta é "200"
+ And a lista com ID "789" é excluída
+
+Scenario: Tentar criar uma lista sem nome
+ When uma requisição "POST" é enviada para "/lists" com os seguintes dados:
+ """
+ {
+ }
+ """
+ Then o status de resposta é "401"
+ And a resposta é "Digite um nome para sua lista"
+
+
diff --git a/features/login.feature b/features/login.feature
new file mode 100644
index 0000000000..68c75fcbca
--- /dev/null
+++ b/features/login.feature
@@ -0,0 +1,81 @@
+Cenarios de Gui:
+
+Scenario:
+Given eu estou na página inicial do sistema
+When eu seleciono “Cadastre-se já”
+Then eu sou redirecionado para uma tela de criação de novo usuário
+And eu preencho o Email como “joao@gmail.com”
+And eu preencho o CPF como “123.432.542-87
+And eu preencho a senha como “#Senha123“
+And eu preencho a confirmação de senha como “# Senha123“
+And eu envio
+Then eu sou redirecionado para a página inicial
+And eu sou capaz de fazer o login com o meu novo usuário
+
+Scenario 2: Usuário solicita a recuperação de conta
+
+Given que eu não estou logado com nenhum usuário do sistema
+And eu estou na página "Login"
+When eu clico no botão "Esqueci minha Senha"
+Then eu sou encaminhado para a página "Recuperação de conta"
+
+Scenario 3: O usuário recupera sua conta
+
+Given eu estou na página “Recuperação de conta”
+When eu vejo o campo de e-mail.
+And eu preencho o campo de email com “João@gmail.com”
+Then eu recebo um email para redefinir minha senha
+When eu preencho o campo “Nova Senha”
+And clico no botão “Confirmar”
+Then eu sou redirecionado para página de “login”
+And eu sou capaz de fazer login com minha nova senha.
+
+
+Scenario 4: A senha ou o login do usuário não conseguem ser identificados
+
+Given eu estou na página inicial do sistema
+And a senha “123” não corresponde ao usuário “João”
+When eu seleciono a opção “Login”
+And eu preencho o campo de usuário com “João”
+And eu preencho o campo da senha com “123”
+And eu clico no botão “Confirmar”
+Then a seguinte mensagem aparece: “Credenciais inválidas.Verifique seu nome de usuário e senha e tente novamente“
+
+Scenario 7: Senha não cumpre os requisitos mínimos no cadastro
+
+Given eu estou na página de cadastro do sistema
+When eu preencho o campo de usuário com “joão”
+And preencho o campo de email com “joao@gmail.com”
+And preencho o campo de senha com “senha123”
+And preencho o campo de confirmar senha com “senha123”
+And preencho o campo de CPF
+And clico em “Enviar”
+Then recebo o aviso:” A senha deve conter no Mínimo uma Letra maiúscula e um caracter especial”
+
+Scenario 8: o Usuário loga no sistema
+
+Scenario:
+Given que eu não estou logado com nenhum usuário do sistema
+And meu login é “joão”
+And minha senha é “#Senha123”
+When seleciono a opção “Já tenho conta”
+And preencho o usuário como “joão”
+And preencho a senha como “#Senha123”
+And eu confirmo
+Then consigo logar na minha conta
+
+Cenarios de Serviço:
+
+Scenario:criar usuário
+Given não existe um usuário cadastrado com o nome "Joao", email "joao@gmail.com" e senha "Joao1245&"
+When uma requisição "POST" foi enviada para "/user"
+And o body da requisição tem o nome "Joao, email "joao@gmail.com" e senha "Joao1245&"
+Then o status de resposta é "200"
+And um usuário é cadastrado com nome "Joao", email "joao@gmail.com" e senha "Joao1245&"
+
+Scenario:Logar o usuário
+Given existe um usuário com nome "Joao", email "joao@gmail.com" e senha "Joao1245&"
+When uma requisição "POST" foi enviada para "/user"
+And o body da requisição tem o nome email "joao@gmail.com" e senha "Joao1245&"
+Then o status de resposta é "200"
+And o usuário é logado no sistema
\ No newline at end of file
diff --git a/features/restaurant.feature b/features/restaurant.feature
new file mode 100644
index 0000000000..047b7de9cb
--- /dev/null
+++ b/features/restaurant.feature
@@ -0,0 +1,101 @@
+Feature: Restaurant
+
+## Cenários de GUI
+
+ Scenario: cadastrar restaurante novo GUI
+ Given eu estou na página "cadastro de restaurante"
+ And o restaurante "Marcelinho Salgados" não está cadastrada no site
+ When eu defino o nome do restaurante como "Marcelinho Salgados"
+ And eu defino o endereço como "Av. Jorn. Aníbal Fernandes, 150 - Cidade Universitária, Recife"
+ And eu defino tipo de comida como "salgados"
+ And eu salvo o cadastro
+ Then eu estou na página "visualização de conteúdo"
+ And eu vejo a mensagem "Marcelinho Salgados foi cadastrado com sucesso"
+ And eu vejo "Marcelinho Salgados" como nome do restaurante
+ And eu vejo "salgados" como tipo de comida
+ And eu vejo "Av. Jorn. Aníbal Fernandes, 150 - Cidade Universitária, Recife" como endereço
+
+
+ Scenario: editar restaurante cadastrado
+ Given eu estou na página "visualização de restaurante"
+ And eu vejo "Domino's" como nome do restaurante
+ And eu vejo "confeitaria" como tipo de comida
+ When eu seleciono edição
+ And eu atualizo o tipo de comida para "pizza"
+ And salvo a edição
+ Then eu vejo a mensagem "Edição realizada com sucesso"
+ And eu vejo "Domino's" como nome do restaurante
+ And eu vejo "pizza" como tipo de comida
+
+ Scenario: deletar restaurante cadastrado
+ Given eu estou na página "visualização de restaurante"
+ And eu vejo "Almir refeições" como nome do restaurante
+ And eu vejo "Recife, Cidade Universitária" como nome do restaurante
+ When eu deleto "Almir refeições"
+ Then eu vejo a mensagem "Almir refeições foi deletado com sucesso"
+ And eu estou na página "restaurantes"
+ And não vejo "Almir refeições" com localização "Recife, Cidade Universitária"
+
+ Scenario: cadastrar restaurante repetido
+ Given eu estou na página "cadastro de restaurante"
+ And o restaurante "Brazzetus" com endereço "R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife" já está cadastrada no site
+ And eu defino o nome do restaurante como "Brazzetus"
+ And eu defino o endereço como "R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife"
+ When eu salvo o cadastro
+ Then eu vejo a mensagem "Brazzetus (R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife) já foi cadastrado por outro usuário"
+
+ Scenario: visualizar restaurante
+ Given eu estou na página "restaurantes"
+ And vejo o restaurante "Brazzetus" com endereço "R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife"
+ When eu seleciono "Brazzetus" com endereço "R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife"
+ Then eu estou na página "visualização de restaurante"
+ And eu vejo "Brazzetus" como nome do restaurante
+ And eu vejo "R. Profa. Argemira Rêgo Barros, 144 - Várzea, Recife" como endereço
+
+
+ Scenario: cadastrar restaurante sem nome
+ Given eu estou na página "cadastro de conteúdo"
+ And o campo "nome" está vazio
+ And eu defino o endereço como "R. Setúbal, 1324, Boa Viagem - Recife"
+ When eu salvo o cadastro
+ Then eu vejo a mensagem "Restaurante sem título não pode ser cadastrado"
+
+
+ Scenario: cadastrar restaurante sem localização
+ Given eu estou na página "cadastro de restaurante"
+ And o campo "bairro" está vazio
+ And eu preencho defino o nome do restaurante como "Tay San"
+ And eu defino tipo de comida como "asiática"
+ When eu salvo o cadastro
+ Then eu vejo a mensagem "Restaurante sem endereço não pode ser cadastrado"
+
+## Cenários de Serviço
+
+ Scenario: Obter restaurante por ID
+ Given existe um restaurante cadastrado com id "1234" e nome "Marcelinho Salgado"
+ When uma requisição "GET" foi enviada para "/restaurants/1234"
+ Then o status de resposta é "200"
+ And a resposta contém id "1234" e nome "Marcelinho Salgado"
+
+ Scenario: Editar restaurante por ID
+ Given existe um restaurante cadastrado com id "5432" e nome "Best Pizza"
+ When uma requisição "PUT" foi enviada para "/restaurants/edit/5432"
+ And o body da requisição contém nome "Worst Pizza"
+ Then o status de resposta é "200"
+ And existe um restaurante cadastrado com id "5432" e nome "Worst Pizza"
+ And não existe restaurante cadastrado com id "5432" e nome "Best Pizza"
+
+ Scenario: cadastrar restaurante novo serviço
+ Given não existe um restaurante cadastrado com nome "Marcelinho Salgado" e endereço "Rua dos Reitores, 220 - Várzea, Recife"
+ When uma requisição "POST" foi enviada para "/restaurants"
+ And o body da requisição contém nome "Marcelinho Salgado", endereço "Rua dos Reitores, 220 - Várzea, Recife" e tipo de comida "Salgados"
+ Then o status de resposta é "200"
+ And um restaurante é cadastrado com nome "Marcelinho Salgado", endereço "Rua dos Reitores, 220 - Várzea, Recife" e tipo de comida "Salgados"
+
+ Scenario: erro ao cadastrar restaurante existente
+ Given existe um restaurante cadastrado com nome "Marcelinho Salgado" e endereço "Rua dos Reitores, 220 - Várzea, Recife"
+ When uma requisição "POST" foi enviada para "/restaurants"
+ And o body da requisição contém nome "Marcelinho Salgado", endereço "Rua dos Reitores, 220 - Várzea, Recife" e tipo de comida "Salgados"
+ Then o status de resposta é "400"
+ And a resposta é "Restaurante já cadastrado"
+
diff --git a/features/reviews.feature b/features/reviews.feature
new file mode 100644
index 0000000000..60fbd05a07
--- /dev/null
+++ b/features/reviews.feature
@@ -0,0 +1,178 @@
+Cenários de GUI:
+
+Cenário 1 - Adicionar Review (completo)
+Given Eu estou logado com o usuário “Pedro Monte”
+And Eu estou na aba do restaurante “Casa dos Doces”
+And Eu vejo a informação “11 reviews” com nota média de 4 estrelas
+When Eu aperto em “Fazer review”
+And Surge uma janela de preenchimento de review
+And Eu preencho o título “O melhor bem casado da cidade!”
+And Eu preencho o texto do review "Doces perfeitos. Coxinha nota 10"
+And Eu dou a nota “5” para o restaurante “Casa dos Doces”
+And Eu clico em “Enviar”
+And Aparece uma mensagem “Concluído”
+And Eu vejo a informação “12 reviews” com nota média de 4 estrelas
+And Eu clico em "Reviews de Usuários"
+Then Eu vejo o review "O melhor bem casado da cidade"
+
+Cenário 2 - Adicionar Nota
+Given Eu estou logado com o usuário “Pedro Monte”
+And Eu estou na aba do restaurante “Casa dos Doces”
+And Eu vejo a informação “11 reviews” com nota média de 4 estrelas
+And Eu vejo a região “Faça uma avaliação” com 5 estrelas cinzas
+When Eu aperto em 4 estrelas
+Then As 4 estrelas ficam amarelas
+And O número de avaliações sobe para “11 reviews”
+And A nota mantém em 4 estrelas
+
+Cenário 3 - Visualização dos Reviews de um Restaurante
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na aba do restaurante “Casa dos Doces”
+When Eu aperto em “Reviews de Usuários”
+Then Abre uma aba com reviews de usuários
+And Eu vejo o review "O melhor bem casado da cidade!" de Pedro Monte, com nota 5 e 5 likes
+And Eu vejo o review "Coxinha Fria" de Maria Letícia, com nota 3 e 1 like e 3 deslikes
+
+Cenário 4 - Visualização de um Review de um usuário
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na aba de “Reviews de Usuários” do restaurante “Casa dos Doces”
+And Eu vejo o review “Coxinha fria” escrito por “Maria Letícia”
+And Eu clico em “Ver Mais”
+Then Surge uma página com mais informações do review
+And Eu vejo a nota 3 estrelas
+And Eu vejo a foto de uma coxinha
+And Eu vejo a nota 2 estrelas dadas para “Sabor”
+And Eu vejo a nota 3 estrelas dadas para “Tempo de Espera”
+And Eu vejo a nota 5 estrelas dadas para “Atendimento”
+And Eu vejo 3 cifrões dados para “Preço”
+And Eu vejo 5 likes
+
+Cenário 5 - Visualização de um Review criado pelo usuário logado
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na aba de “Reviews de Usuários” do restaurante “Casa dos Doces”
+And Eu vejo o review "O melhor bem-casado da cidade!" por Pedro Monte
+And Eu clico em "Ver Mais"
+Then Surge uma página do review "O melhor bem-casado da cidade!"
+And Eu vejo a nota 5 estrelas
+And Eu vejo a foto de um bem casado
+And Eu vejo a nota 5 estrelas dadas para “Sabor”
+And Eu vejo a nota 5 estrelas dadas para “Tempo de Espera”
+And Eu vejo a nota 5 estrelas dadas para “Atendimento”
+And Eu vejo 3 cifrões dados para “Preço”
+And Eu vejo 1 like e 3 deslikes
+And Eu vejo a opção “Editar Review”
+
+Cenário 6 - Edição de Review (completo)
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na página do review “Coxinha Fria” escrita por “Maria Letícia” do restaurante “Casa dos Doces”
+When Eu clico na opção “Editar Review”
+And Surge uma aba de editar review
+And Eu mudo o título do review para “Coxinha Boa”
+And Eu mudo a nota para 4 estrelas
+And Eu clico em “Salvar”
+Then Surge uma mensagem escrito “Concluído”
+And Eu volto para a página do review
+And Eu vejo o título "Coxinha boa"
+And Eu vejo a nota 4 estrelas
+
+Cenário 7 - Remoção de Review
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na página do review “Coxinha Fria” escrita por “Maria Letícia” do restaurante “Casa dos Doces”
+When Eu clico na opção “Editar Review”
+And Eu clico no botão “Excluir”
+Then Surge uma mensagem “Review excluído com sucesso”
+And Eu vejo o review "Brigadeiro belga delicioso" de Ana Sofia no lugar de "Coxinha fria"
+
+Cenário 8 - Dar Like num review
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na aba de “Reviews de Usuários” do restaurante “Casa dos Doces”
+And Eu vejo o review “O melhor bem casado da cidade!” escrito por “Pedro Monte”
+And Eu vejo que o review tem 5 likes e 0 deslikes
+When Eu clico no botão de like
+Then O botão de like fica preto
+And O número de likes sobe para 6
+
+Cenário 9 - Dar Deslike num review
+Given Eu estou logado com o usuário “Maria Letícia”
+And Eu estou na aba de “Reviews de Usuários” do restaurante “Casa dos Doces”
+And Eu vejo o review “Coxinha fria” escrito por “Maria Leticia”
+And Eu vejo que o review tem 1 like e 3 deslikes
+When Eu clico no botão de deslike
+Then O botão de deslike fica preto
+And O número de deslikes sobe para 4
+
+Cenário 10 - Visualizar Reviews de um Usuário pelo Perfil
+Given Eu estou logado com o usuário “Guilherme Maranhão”
+And Eu vejo a opção Reviews(13)
+When Eu clico em “Reviews(13)”
+Then Eu vou para uma página contendo todos os reviews feitos pelo usuário “Guilherme Maranhão”
+And Eu vejo o review "Torta perfeita!!" para o restaurante Casa dos Doces
+And Eu vejo o review "Melhores salgados da vida" para o restaurante Marcelinho dos Salgados
+
+Cenários de Serviço:
+
+Cenário 1 - Visualização de um Review
+Given O restaurante de ID "65d29514713ed7cc6fcf3635" contém um review feito pelo usuário de ID "15d29514713ed7cc6fcf3635" de título "Coxinha Boa", texto "Boa Coxinha" e nota "5"
+When é feita uma requisição GET para "/reviews/65d29514713ed7cc6fcf3635/15d29514713ed7cc6fcf3635"
+Then O status da resposta deve ser "200"
+And Deve ser retornado um JSON com o review "Coxinha Boa"
+
+Cenário 2 - Adicionar Nota
+Given Não existe nota dada para o restaurante de ID "30" pelo usuário de ID "40"
+When Uma requisição POST é feita para "/reviews/30/40" com a nota "5"
+Then O status da resposta deve ser "200"
+And A nota "5" associada ao usuário "40" e restaurante "30" está no banco de dados
+
+Cenário 3 - Criar Review
+Given Não existe review feito pelo usuário de ID "00d29514713ed7cc6fcf3635" no restaurante de ID "65d29514713ed7cc6fcf3635"
+When Uma requisição POST é feita para "/reviews/65d29514713ed7cc6fcf3635/00d29514713ed7cc6fcf3635" com título "Coxinha Bem Ruim", texto "Bem Ruim Coxinha" e nota "1"
+Then O status da resposta deve ser "200"
+And O review "Coxinha Bem Ruim" associada ao usuário "00d29514713ed7cc6fcf3635" e restaurante "65d29514713ed7cc6fcf3635" está no banco de dados
+
+Cenário 4 - Obter Média de Notas de um Restaurante
+Given O restaurante de ID "65d29514713ed7cc6fcf3635" contém três notas dadas em reviews: "5" pelo user "15d29514713ed7cc6fcf3635", "4" pelo user "02d29514713ed7cc6fcf3635" e "3" pelo user "03d29514713ed7cc6fcf3635"
+When Uma requisição GET é feita para "/reviews/65d29514713ed7cc6fcf3635/avg"
+Then O status de resposta deve ser "200"
+And A resposta deve ser média "4"
+
+Cenário 5 - Obter lista de reviews de um restaurante
+Given O restaurante de id "65d29514713ed7cc6fcf3635" contém três reviews "Coxinha Boa", "Coxinha ok" e "Coxinha meh"
+When é feita uma requisição GET para "/reviews/65d29514713ed7cc6fcf3635"
+Then O status da resposta deve ser "200"
+And Deve ser retornado um JSON com os três reviews "Coxinha Boa", "Coxinha ok" e "Coxinha meh"
+
+Cenário 6 - Edição de Review
+Given O restaurante de ID "65d2cfb1620894960053d364" contém um review feito pelo usuário de ID "25d29514713ed7cc6fcf3635" e título "Coxinha Ruim"
+When é feita uma requisição PUT para "/reviews/65d2cfb1620894960053d364/25d29514713ed7cc6fcf3635" alterando o título para "Coxinha Mais ou Menos"
+Then O status da resposta deve ser "200"
+And Deve ser retornado um JSON contendo o review "Coxinha Mais ou Menos"
+
+Cenário 7- Remoção de Review
+Given O restaurante de ID "65d2cfb1620894960053d364" contém um review feito pelo usuário de ID "05d29514713ed7cc6fcf3635" e título "Coxinha eh"
+When é feita uma requisição DELETE para "/reviews/65d2cfb1620894960053d364/05d29514713ed7cc6fcf3635"
+Then O status da resposta deve ser "200"
+And O review "Coxinha eh" do restaurante de ID "65d2cfb1620894960053d364" e usuário de ID "05d29514713ed7cc6fcf3635" não deve constar no banco de dados
+
+Cenário 8 - Visualizar Reviews a partir de um user
+Given O usuário de ID "15d29514713ed7cc6fcf3635" contém dois reviews feitos "Coxinha Boa" e "Ótimo Sushi"
+When É feita uma requisição GET para "/reviews/user/15d29514713ed7cc6fcf3635"
+Then O status da resposta deve ser "200"
+And Deve retornar um JSON contendo os dois reviews "Coxinha Boa" e "Ótimo Sushi"
+
+Cenário 9 - Criar review dado que uma nota já existe
+Given O usuário de ID "40" deu a nota "4" para o restaurante de ID "30"
+When É feita uma requisição POST para "/reviews/30/40" de título "Coxinha Boa" e nota "4.5"
+Then O status da resposta deve ser "200"
+And O JSON de retorno etornar o JSON do review "Coxinha Boa"
+And Deve retornar o JSON com a nota atualizada "4.5"
+
+Cenário 10 - Acessar Review Inexistente
+Given Não há review feito pelo usuário de ID "40" no restaurante de ID "30"
+When É feita uma requisição GET para "/reviews/30/40"
+Then O status da resposta deve ser "404"
+
+Cenário 11 - Obter nota
+Given O restaurante de ID "30" contém uma nota "5" dada pelo usuário de ID "40"
+When é feita uma requisição GET para "/reviews/30/40"
+Then O status da resposta deve ser "200"
+And Deve ser retornado um JSON com a nota "5"
\ No newline at end of file
diff --git a/features/searchForUser.feature b/features/searchForUser.feature
new file mode 100644
index 0000000000..7608e7dbaa
--- /dev/null
+++ b/features/searchForUser.feature
@@ -0,0 +1,14 @@
+Scenario 5: O usuário busca por alguém
+
+Given eu estou na página dos usuários
+When eu seleciono a barra de busca
+And digito “joão”
+Then terei na minha tela todos os usuários com nome joão
+
+Scenario 6: Indo para página do usuário
+
+Given eu estou logado no sistema
+And eu estou na página inicial do sistema
+When eu seleciono a opção “Usuários”
+Then terei uma lista com todos os usuários do sistema
+And poderei buscar pelo nome
diff --git a/frontend/.gitignore b/frontend/.gitignore
new file mode 100644
index 0000000000..4d29575de8
--- /dev/null
+++ b/frontend/.gitignore
@@ -0,0 +1,23 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
diff --git a/frontend/README.md b/frontend/README.md
new file mode 100644
index 0000000000..58beeaccd8
--- /dev/null
+++ b/frontend/README.md
@@ -0,0 +1,70 @@
+# Getting Started with Create React App
+
+This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
+
+## Available Scripts
+
+In the project directory, you can run:
+
+### `npm start`
+
+Runs the app in the development mode.\
+Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
+
+The page will reload when you make changes.\
+You may also see any lint errors in the console.
+
+### `npm test`
+
+Launches the test runner in the interactive watch mode.\
+See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
+
+### `npm run build`
+
+Builds the app for production to the `build` folder.\
+It correctly bundles React in production mode and optimizes the build for the best performance.
+
+The build is minified and the filenames include the hashes.\
+Your app is ready to be deployed!
+
+See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
+
+### `npm run eject`
+
+**Note: this is a one-way operation. Once you `eject`, you can't go back!**
+
+If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
+
+Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
+
+You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
+
+## Learn More
+
+You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
+
+To learn React, check out the [React documentation](https://reactjs.org/).
+
+### Code Splitting
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
+
+### Analyzing the Bundle Size
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
+
+### Making a Progressive Web App
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
+
+### Advanced Configuration
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
+
+### Deployment
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
+
+### `npm run build` fails to minify
+
+This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
diff --git a/frontend/cypress.config.js b/frontend/cypress.config.js
new file mode 100644
index 0000000000..a32ae0304d
--- /dev/null
+++ b/frontend/cypress.config.js
@@ -0,0 +1,11 @@
+const cucumber = require('cypress-cucumber-preprocessor').default
+const { defineConfig } = require("cypress");
+
+module.exports = defineConfig({
+ e2e: {
+ setupNodeEvents(on, config) {
+ on('file:preprocessor', cucumber())
+ },
+ specPattern: "cypress/e2e/step_definitions/*.feature"
+ },
+});
diff --git a/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo.feature b/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo.feature
new file mode 100644
index 0000000000..bf2d04f5f0
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo.feature
@@ -0,0 +1,9 @@
+Feature: Followers
+Scenario: Deixar de seguir um usuário a partir da lista de seguindo
+ Given estou logada como "Joaninha", com email "tijb@mail.com" e senha "p@ssWord123"
+ And eu tenho "2 SEGUINDO"
+ When eu visualizo a lista de usuários que sigo
+ And eu deixo de seguir "Leticia"
+ And volto para a minha página
+ Then eu tenho "1 SEGUINDO"
+
diff --git a/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo/deixarSeguirListaSeguindo.js b/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo/deixarSeguirListaSeguindo.js
new file mode 100644
index 0000000000..50b6d21a6e
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/deixarSeguirListaSeguindo/deixarSeguirListaSeguindo.js
@@ -0,0 +1,45 @@
+import{ Given, When, Then } from "cypress-cucumber-preprocessor/steps"
+
+let initialFollowingCount;
+
+Given('estou logada como {string}, com email {string} e senha {string}', (name, email, senha) => {
+ cy.visit('http://localhost:3000/login');
+ cy.get('.input-field1').clear().type(email);
+ cy.get('.input-field2').clear().type(senha);
+ cy.get('.loginbutton').click();
+ cy.get('.nameuser').should('contain', name);
+});
+
+Given('eu tenho "2 SEGUINDO"', () => {
+ cy.get('.followinguser:contains(" SEGUINDO")')
+ .invoke('text').then((initialFollowing) => {
+ initialFollowingCount = parseInt(initialFollowing);
+ });
+});
+
+When('eu visualizo a lista de usuários que sigo', () => {
+ cy.get('[data-cy=num-seguindo]').click();
+});
+
+When('eu deixo de seguir {string}', (name) => {
+ cy.get('.unit-info-follow')
+ .contains(name)
+ .parent()
+ .parent()
+ .parent()
+ .within(() => {
+ cy.get('.unit-buttons-follow').should('exist');
+ cy.get('.unfollow-button').should('exist');
+ cy.get('.unfollow-button').click();
+ });
+});
+
+When('volto para a minha página', () => {
+ cy.get('.close-button-follow').click();
+});
+
+Then('eu tenho "1 SEGUINDO"', () => {
+ cy.get('.followinguser:contains(" SEGUINDO")')
+ .invoke('text')
+ .should('contain', (initialFollowingCount - 1).toString());
+});
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/edituser.feature b/frontend/cypress/e2e/step_definitions/edituser.feature
new file mode 100644
index 0000000000..c7bd6534e6
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/edituser.feature
@@ -0,0 +1,41 @@
+Feature: User Edit
+
+ Scenario: Troca de nome com sucesso
+
+ Given estou na tela "users/edit" logado como "taylors@gmail.com"
+ And eu vejo meu nome "taylors"
+ When eu preencho o campo Nome de Perfil com "taylorrrs" e confirmo minha mudança
+ Then eu sou redirecionado minha para página de perfil
+ And eu vejo meu nome alterado "taylorrrs"
+
+ Scenario: Troca de senha com sucesso
+
+ Given estou na tela "users/edit" logado com "taylors@gmail.com" e senha "Aaaa123!"
+ When eu preencho o campo Senha atual com "Aaaa123!" e o campo Nova senha com "aaaA123!" e o campo Confirmar nova senha com "aaaA123!"
+ Then eu sou redirecionado página de login
+ When eu faço login com email "taylors@gmail.com" e senha "aaaA123!"
+ Then eu sou redirecionado para minha página de perfil
+
+ Scenario: Troca de senha com falha
+
+ Given estou na tela "users/edit" e logado com "taylors@gmail.com" e senha "aaaA123!"
+ When eu preencho os campos Senha atual com "aaaA123!" e o campo Nova senha com "123456" e o campo Confirmar nova senha com "123456"
+ Then eu recebo uma mensagem de erro
+ And permaneço na mesma página
+ When eu faço login novamente com email "taylors@gmail.com" e senha "123456"
+ Then não consigo entrar na minha página
+
+ Scenario: Deletar conta com falha
+
+ Given estou na tela de "users/edit" logado com "taylors@gmail.com" e meu nome é "taylorrrs"
+ When eu preencho os campos Digite sua senha com "aaaA123!" e o campo Confirme sua senha com "123456" e confirmo
+ Then eu recebo uma mensagem de erro
+ And eu permaneço logado como "taylorrrs"
+
+ Scenario: Deletar conta com sucesso
+
+ Given estou na tela da "users/edit" logado com "taylors@gmail.com"
+ When eu preencho o campo Digite sua senha com "aaaA123!" e o campo Confirme sua senha com "aaaA123!" e confirmo
+ Then eu sou redirecionado para página de login
+ When eu faço um login mais uma vez com email "taylors@gmail.com" e senha "aaaA123!"
+ Then não consigo entrar na minha página
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/edituser/edituser.js b/frontend/cypress/e2e/step_definitions/edituser/edituser.js
new file mode 100644
index 0000000000..3e0f2c718e
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/edituser/edituser.js
@@ -0,0 +1,188 @@
+import {Given, When, Then} from 'cypress-cucumber-preprocessor/steps'
+
+Given("estou na tela {string} logado como {string}", (url, email) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type("Aaaa123!")
+ cy.get('.loginbutton').click()
+ cy.get('.buttonedit').click()
+ cy.get('.profileuseredit').click()
+ cy.url().should('include', url)
+
+})
+
+And("eu vejo meu nome {string}", (nome) => {
+
+ cy.get('.usernameuseredit').should('contain', nome)
+
+})
+
+When("eu preencho o campo Nome de Perfil com {string} e confirmo minha mudança", (required) => {
+
+ cy.get('.nomeusuario').clear().type(required)
+ cy.get('.botaoconfirmar').click()
+
+})
+
+Then("eu sou redirecionado minha para página de perfil", () => {
+
+ cy.url().should('include', '/users')
+
+})
+
+And("eu vejo meu nome alterado {string}", (nome) => {
+
+ cy.get('.nameuser').should('contain', nome)
+
+})
+
+Given("estou na tela {string} logado com {string} e senha {string}", (url, email, senha) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(senha)
+ cy.get('.loginbutton').click()
+ cy.get('.buttonedit').click()
+ cy.get('.passworduseredit').click()
+ cy.url().should('include', url)
+
+})
+
+When("eu preencho o campo Senha atual com {string} e o campo Nova senha com {string} e o campo Confirmar nova senha com {string}", (cursenha, novasenha, confirm) => {
+
+ cy.get('.inputfrasesenha').clear().type(cursenha)
+ cy.get('.inputfrasenovasenha').clear().type(novasenha)
+ cy.get('.inputconfirmarsenha').clear().type(confirm)
+ cy.get('.confirmartroca').click()
+
+})
+
+Then("eu sou redirecionado página de login", () => {
+
+ cy.url().should('include', '/login')
+
+})
+
+When("eu faço login com email {string} e senha {string}", (email, novasenha) => {
+
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(novasenha)
+ cy.get('.loginbutton').click()
+
+})
+
+Then("eu sou redirecionado para minha página de perfil", () => {
+
+ cy.get('.profileInfo')
+
+})
+
+
+Given("estou na tela {string} e logado com {string} e senha {string}", (url, email, senha) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(senha)
+ cy.get('.loginbutton').click()
+ cy.get('.buttonedit').click()
+ cy.get('.passworduseredit').click()
+ cy.url().should('include', url)
+
+})
+
+When("eu preencho os campos Senha atual com {string} e o campo Nova senha com {string} e o campo Confirmar nova senha com {string}", (cursenha, novasenha, confirm) =>{
+
+ cy.get('.inputfrasesenha').clear().type(cursenha)
+ cy.get('.inputfrasenovasenha').clear().type(novasenha)
+ cy.get('.inputconfirmarsenha').clear().type(confirm)
+ cy.get('.confirmartroca').click()
+
+})
+
+Then("eu recebo uma mensagem de erro", () =>{
+ cy.get('.tudosenha')
+})
+
+And("permaneço na mesma página", () => {
+ cy.get('.tudosenha')
+})
+
+When("eu faço login novamente com email {string} e senha {string}", (email, senha) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(senha)
+ cy.get('.loginbutton').click()
+
+})
+
+Then("não consigo entrar na minha página", () => {
+
+ cy.url().should('include', '/login')
+
+})
+
+Given("estou na tela de {string} logado com {string} e meu nome é {string}", (url, email, nome) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type("aaaA123!")
+ cy.get('.loginbutton').click()
+ cy.get('.buttonedit').click()
+ cy.get('.advanceduseredit').click()
+ cy.get('.usernameuseredit').should('contain', nome)
+ cy.url().should('include', url)
+
+})
+
+When("eu preencho os campos Digite sua senha com {string} e o campo Confirme sua senha com {string} e confirmo", (senha, senhaerrada) => {
+
+ cy.get('.inputfrasesenha').clear().type(senha)
+ cy.get('.inputconfirmarsenha').clear().type(senhaerrada)
+ cy.get('.confirmardelete').click()
+
+})
+
+Then("eu recebo uma mensagem de erro", () => {
+ cy.get('.tudosenha')
+})
+
+And("eu permaneço logado como {string}", (nome) => {
+ cy.get('.usernameuseredit').should('contain', nome)
+})
+
+Given("estou na tela da {string} logado com {string}", (url, email) => {
+
+ cy.visit("http://localhost:3000/login")
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type("aaaA123!")
+ cy.get('.loginbutton').click()
+ cy.get('.buttonedit').click()
+ cy.get('.advanceduseredit').click()
+ cy.url().should('include', url)
+
+})
+
+When("eu preencho o campo Digite sua senha com {string} e o campo Confirme sua senha com {string} e confirmo", (senha, cfsenha) => {
+
+ cy.get('.inputfrasesenha').clear().type(senha)
+ cy.get('.inputconfirmarsenha').clear().type(cfsenha)
+ cy.get('.confirmardelete').click()
+
+})
+
+Then("eu sou redirecionado para página de login", () => {
+
+ cy.url().should('include', '/login')
+
+})
+
+When("eu faço um login mais uma vez com email {string} e senha {string}", (email, senha) => {
+
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(senha)
+ cy.get('.loginbutton').click()
+ cy.url().should('include', '/login')
+
+})
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/login.feature b/frontend/cypress/e2e/step_definitions/login.feature
new file mode 100644
index 0000000000..f4835a1fb2
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/login.feature
@@ -0,0 +1,12 @@
+Feature: Login
+
+ Scenario: Successful login
+
+ Given estou na página de login
+ When eu preencho o campo e-mail com "omena@gmail.com" e senha com "Aaaa123!"
+ Then eu tenho um login de sucesso
+
+ Scenario:Falha no login com credenciais inválidas
+ Given usuário está na página login
+ When o usuário insere o email "omena@gmail.com" e a senha "senhaerrada"
+ Then eu tenho um login falhado
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/login/login.js b/frontend/cypress/e2e/step_definitions/login/login.js
new file mode 100644
index 0000000000..3fe6439f46
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/login/login.js
@@ -0,0 +1,34 @@
+import {Given, When, Then} from 'cypress-cucumber-preprocessor/steps'
+
+Given("estou na página de login", () => {
+ cy.visit("http://localhost:3000/login")
+})
+
+When("eu preencho o campo e-mail com {string} e senha com {string}", (email, senha) => {
+ cy.get('.input-field1').clear().type(email)
+ cy.get('.input-field2').clear().type(senha)
+ cy.get('.loginbutton').click()
+})
+
+Then("eu tenho um login de sucesso", () => {
+ cy.get('.profileInfo')
+})
+
+
+
+
+
+
+Given("usuário está na página login", () => {
+ cy.visit("http://localhost:3000/login");
+});
+
+When("o usuário insere o email {string} e a senha {string}", (email, senha) => {
+ cy.get('.input-field1').clear().type(email);
+ cy.get('.input-field2').clear().type(senha);
+ cy.get('.loginbutton').click();
+});
+
+Then("eu tenho um login falhado", () => {
+ cy.get('.loginfalha').should('be.visible');
+});
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil.feature b/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil.feature
new file mode 100644
index 0000000000..97d8668d24
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil.feature
@@ -0,0 +1,7 @@
+Feature: Followers
+Scenario: Acessar o perfil de um usuário a partir da página de outro
+ Given estou logada como "Joaninha", com email "tijb@mail.com" e senha "p@ssWord123"
+ And estou na página de "Guilherme Maranhão", no link "http://localhost:3000/users/65d1eb21077a9668192c4fe8"
+ When eu clico nos usuário que segue
+ And eu seleciono "Ver perfil" de "Joaozinho"
+ Then eu estou na página de perfil de "Joaozinho"
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil/perfilPorOutroPerfil.js b/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil/perfilPorOutroPerfil.js
new file mode 100644
index 0000000000..8120b68f7a
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/perfilPorOutroPerfil/perfilPorOutroPerfil.js
@@ -0,0 +1,36 @@
+import{ Given, When, Then } from "cypress-cucumber-preprocessor/steps"
+
+Given('estou logada como {string}, com email {string} e senha {string}', (name, email, senha) => {
+ cy.visit('http://localhost:3000/login');
+ cy.get('.input-field1').clear().type(email);
+ cy.get('.input-field2').clear().type(senha);
+ cy.get('.loginbutton').click();
+ cy.get('.nameuser').should('contain', name);
+});
+
+Given('estou na página de {string}, no link {string}', (name, page) => {
+ cy.visit(page);
+ cy.get('.nameuser').should('contain', name);
+
+});
+
+When('eu clico nos usuário que segue', () => {
+ cy.get('[data-cy=num-seguindo]').click();
+});
+
+When('eu seleciono "Ver perfil" de {string}', (name) => {
+ cy.get('.unit-info-follow')
+ .contains(name)
+ .parent()
+ .parent()
+ .parent()
+ .within(() => {
+ cy.get('.unit-buttons-follow').should('exist');
+ cy.get('.view-button-follow').click();
+ });
+
+})
+
+When('eu estou na página de perfil de {string}', (name) => {
+ cy.get('.nameuser').should('contain', name);
+})
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil.feature b/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil.feature
new file mode 100644
index 0000000000..e97b12a450
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil.feature
@@ -0,0 +1,9 @@
+Feature: Followers
+Scenario: Seguir usuário a partir da página de perfil
+ Given estou logada como "Joaninha", com email "tijb@mail.com" e senha "p@ssWord123"
+ Given estou na página de perfil de "Joaozinho", no "http://localhost:3000/users/65d58c5ec3082d4949f7cd03"
+ Given tem "0 SEGUIDORES"
+ When eu sigo
+ Then a mensagem "Seguiu com sucesso. Uma mensagem foi enviada para o usuário!" aparece
+ Then tem "1 SEGUIDORES"
+ Then eu posso deixar de seguir
diff --git a/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil/seguirUsuarioPerfil.js b/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil/seguirUsuarioPerfil.js
new file mode 100644
index 0000000000..498acb363d
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/seguirUsuarioPerfil/seguirUsuarioPerfil.js
@@ -0,0 +1,41 @@
+import{ Given, When, Then } from "cypress-cucumber-preprocessor/steps"
+
+let initialFollowersCount;
+
+Given('estou logada como {string}, com email {string} e senha {string}', (name, email, senha) => {
+ cy.visit('http://localhost:3000/login');
+ cy.get('.input-field1').clear().type(email);
+ cy.get('.input-field2').clear().type(senha);
+ cy.get('.loginbutton').click();
+ cy.get('.nameuser').should('contain', name);
+});
+
+Given('estou na página de perfil de {string}, no {string}', (name, page) => {
+ cy.visit(page);
+ cy.get('.nameuser').should('contain', name);
+
+});
+
+Given('tem {string}', (seguidores) => {
+ cy.get('.followersuser:contains(" SEGUIDORES")')
+ .invoke('text').then((initialFollowers) => {
+ initialFollowersCount = parseInt(initialFollowers);
+ });
+});
+
+When('eu sigo', () => {
+ cy.get('[data-cy=seguir-profile]').should('be.visible');
+ cy.get('[data-cy=seguir-profile]').click();
+});
+
+Then('a mensagem {string} aparece', (msg) => {
+ cy.get('.modal-follow-body p').invoke('text').should('contain', msg);
+});
+
+Then('tem {string}', (seguidores) => {
+ cy.get('.followersuser:contains(" SEGUIDORES")').invoke('text').should('eq', (initialFollowersCount + 1).toString());
+});
+
+Then('eu posso deixar de seguir', () => {
+ cy.get('[data-cy=deixar-de-seguir-profile]').should('be.visible');
+});
diff --git a/frontend/cypress/e2e/step_definitions/signin.feature b/frontend/cypress/e2e/step_definitions/signin.feature
new file mode 100644
index 0000000000..fe8f063c1a
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/signin.feature
@@ -0,0 +1,11 @@
+Feature:Signin
+
+ Scenario: Signin with valid credentials
+ Given o usuário está na página de cadastro
+ When o usuário insere o nome "joao", o email "mergulhao@gmail.com" e a senha "#Senha123"
+ Then eu tenho um cadastro
+
+ Scenario: Signin with invalid credentials
+ Given o usuário está na página de cadastro
+ When o usuário insere o nome "joao", o email "mergulhao@gmail.com" e a senha "senha123"
+ Then eu tenho um cadastro falho
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/signin/signin.js b/frontend/cypress/e2e/step_definitions/signin/signin.js
new file mode 100644
index 0000000000..972b11f93e
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/signin/signin.js
@@ -0,0 +1,35 @@
+import { Given, When, Then } from 'cypress-cucumber-preprocessor/steps';
+
+Given("o usuário está na página de cadastro", () => {
+ cy.visit("http://localhost:3000/signup");
+});
+
+When("o usuário insere o nome {string}, o email {string} e a senha {string}", (nome, email, senha) => {
+ cy.get('.namesignin').clear().type(nome);
+ cy.get('.emailsignin').clear().type(email)
+ cy.get('#password').clear().type(senha)
+ cy.get('.signupbutton').click();
+});
+
+Then("eu tenho um cadastro", () => {
+ cy.url().should('include', '/login');
+
+});
+
+
+
+Given("o usuário está na página de cadastro", () => {
+ cy.visit("http://localhost:3000/signup");
+});
+
+When("o usuário insere o nome {string}, o email {string} e a senha {string}", (nome, email, senha) => {
+ cy.get('.namesignin').clear().type(nome);
+ cy.get('.emailsignin').clear().type(email)
+ cy.get('#password').clear().type(senha)
+ cy.get('.signupbutton').click();
+});
+
+Then("eu tenho um cadastro falho", () => {
+ cy.url().should('include', '/signup');
+
+});
diff --git a/frontend/cypress/e2e/step_definitions/verListaSeguidores.feature b/frontend/cypress/e2e/step_definitions/verListaSeguidores.feature
new file mode 100644
index 0000000000..2a5c02bd03
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/verListaSeguidores.feature
@@ -0,0 +1,7 @@
+Feature: Followers
+Scenario: Visualização seguidores pela página do usuário
+ Given estou logada como "Joaninha", com email "tijb@mail.com" e senha "p@ssWord123"
+ And o usuário "Roberto" segue "Joaninha"
+ When eu clico em “SEGUIDORES”
+ Then eu deveria ver a lista "Seguidores de Joaninha"
+ And posso ver o usuário "Roberto" na lista
\ No newline at end of file
diff --git a/frontend/cypress/e2e/step_definitions/verListaSeguidores/verListaSeguidores.js b/frontend/cypress/e2e/step_definitions/verListaSeguidores/verListaSeguidores.js
new file mode 100644
index 0000000000..7784d04d15
--- /dev/null
+++ b/frontend/cypress/e2e/step_definitions/verListaSeguidores/verListaSeguidores.js
@@ -0,0 +1,26 @@
+import{ Given, When, Then } from "cypress-cucumber-preprocessor/steps"
+
+Given('estou logada como {string}, com email {string} e senha {string}', (name, email, senha) => {
+ cy.visit('http://localhost:3000/login');
+ cy.get('.input-field1').clear().type(email);
+ cy.get('.input-field2').clear().type(senha);
+ cy.get('.loginbutton').click();
+ cy.get('.nameuser').should('contain', name);
+});
+
+Given('o usuário {string} segue {string}', (name1, name2) => {
+ cy.get('.nameuser').should('contain', name2);
+ cy.get('.nameuser').should('not.contain', name1);
+});
+
+When('eu clico em “SEGUIDORES”', () => {
+ cy.get('[data-cy=num-seguidores]').click();
+});
+
+Then('eu deveria ver a lista {string}', (title) => {
+ cy.get('.list-follow-title').invoke('text').should('contain', title);
+});
+
+Then('posso ver o usuário {string} na lista', (name) => {
+ cy.contains(name).should('exist');
+});
\ No newline at end of file
diff --git a/frontend/cypress/fixtures/example.json b/frontend/cypress/fixtures/example.json
new file mode 100644
index 0000000000..02e4254378
--- /dev/null
+++ b/frontend/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js
new file mode 100644
index 0000000000..66ea16ef0e
--- /dev/null
+++ b/frontend/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
\ No newline at end of file
diff --git a/frontend/cypress/support/e2e.js b/frontend/cypress/support/e2e.js
new file mode 100644
index 0000000000..0e7290a13d
--- /dev/null
+++ b/frontend/cypress/support/e2e.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/e2e.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
\ No newline at end of file
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000000..b160ab8fd0
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,59 @@
+{
+ "name": "frontend",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@react-icons/all-files": "^4.1.0",
+ "@testing-library/jest-dom": "^5.17.0",
+ "@testing-library/react": "^13.4.0",
+ "@testing-library/user-event": "^13.5.0",
+ "axios": "^1.6.7",
+ "bcrypt": "^5.1.1",
+ "express": "^4.18.3",
+ "jwt-decode": "^4.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-icons": "^5.0.1",
+ "react-router-dom": "^6.22.2",
+ "react-scripts": "^5.0.1",
+ "web-vitals": "^2.1.4"
+ },
+ "scripts": {
+ "start": "react-scripts start",
+ "build": "react-scripts build",
+ "test": "react-scripts test",
+ "eject": "react-scripts eject"
+ },
+ "eslintConfig": {
+ "extends": [
+ "react-app",
+ "react-app/jest"
+ ]
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@badeball/cypress-cucumber-preprocessor": "^20.0.2",
+ "@bahmutov/cypress-esbuild-preprocessor": "^2.2.0",
+ "cucumber-html-reporter": "^7.1.1",
+ "cucumber-pretty": "^6.0.1",
+ "cypress": "^13.6.6",
+ "cypress-cucumber-preprocessor": "^4.3.1",
+ "cypress-xpath": "^2.0.1"
+ },
+ "cypress-cucumber-preprocessor": {
+ "nonGlobalStepDefinitions": true,
+ "step_definitions": "cypress/e2e/step_definitions",
+ "esbuild": "^0.20.1"
+ }
+}
diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico
new file mode 100644
index 0000000000..a11777cc47
Binary files /dev/null and b/frontend/public/favicon.ico differ
diff --git a/frontend/public/index.html b/frontend/public/index.html
new file mode 100644
index 0000000000..dc71c3369f
--- /dev/null
+++ b/frontend/public/index.html
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ AlmoCin
+
+
+
+
+
+
+
diff --git a/frontend/public/logo192.png b/frontend/public/logo192.png
new file mode 100644
index 0000000000..fc44b0a379
Binary files /dev/null and b/frontend/public/logo192.png differ
diff --git a/frontend/public/logo512.png b/frontend/public/logo512.png
new file mode 100644
index 0000000000..a4e47a6545
Binary files /dev/null and b/frontend/public/logo512.png differ
diff --git a/frontend/public/logoAlmocin.png b/frontend/public/logoAlmocin.png
new file mode 100644
index 0000000000..3105fdf63c
Binary files /dev/null and b/frontend/public/logoAlmocin.png differ
diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json
new file mode 100644
index 0000000000..080d6c77ac
--- /dev/null
+++ b/frontend/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/frontend/public/no_restaurant_img.jpg b/frontend/public/no_restaurant_img.jpg
new file mode 100644
index 0000000000..7ef27e6e9b
Binary files /dev/null and b/frontend/public/no_restaurant_img.jpg differ
diff --git a/frontend/public/robots.txt b/frontend/public/robots.txt
new file mode 100644
index 0000000000..e9e57dc4d4
--- /dev/null
+++ b/frontend/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/frontend/src/App.js b/frontend/src/App.js
new file mode 100644
index 0000000000..c7261f22ae
--- /dev/null
+++ b/frontend/src/App.js
@@ -0,0 +1,13 @@
+import { Outlet } from 'react-router-dom';
+import Header from './routes/commons/Header';
+
+function App() {
+ return (
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/frontend/src/assets/add.png b/frontend/src/assets/add.png
new file mode 100644
index 0000000000..a769a4a295
Binary files /dev/null and b/frontend/src/assets/add.png differ
diff --git a/frontend/src/assets/almocin_logo_red.png b/frontend/src/assets/almocin_logo_red.png
new file mode 100644
index 0000000000..28d3f040ad
Binary files /dev/null and b/frontend/src/assets/almocin_logo_red.png differ
diff --git a/frontend/src/assets/buttonimageedit.png b/frontend/src/assets/buttonimageedit.png
new file mode 100644
index 0000000000..e830347369
Binary files /dev/null and b/frontend/src/assets/buttonimageedit.png differ
diff --git a/frontend/src/assets/logo.svg b/frontend/src/assets/logo.svg
new file mode 100644
index 0000000000..e4145090ca
--- /dev/null
+++ b/frontend/src/assets/logo.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/logo2.svg b/frontend/src/assets/logo2.svg
new file mode 100644
index 0000000000..6948d11350
--- /dev/null
+++ b/frontend/src/assets/logo2.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/logo3.svg b/frontend/src/assets/logo3.svg
new file mode 100644
index 0000000000..1d565c4318
--- /dev/null
+++ b/frontend/src/assets/logo3.svg
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/assets/no_restaurant_image.jpg b/frontend/src/assets/no_restaurant_image.jpg
new file mode 100644
index 0000000000..de816c046d
Binary files /dev/null and b/frontend/src/assets/no_restaurant_image.jpg differ
diff --git a/frontend/src/assets/noprofileimage.png b/frontend/src/assets/noprofileimage.png
new file mode 100644
index 0000000000..0a12965201
Binary files /dev/null and b/frontend/src/assets/noprofileimage.png differ
diff --git a/frontend/src/assets/page.png b/frontend/src/assets/page.png
new file mode 100644
index 0000000000..60742c9dd8
Binary files /dev/null and b/frontend/src/assets/page.png differ
diff --git a/frontend/src/assets/pencil.png b/frontend/src/assets/pencil.png
new file mode 100644
index 0000000000..131865093c
Binary files /dev/null and b/frontend/src/assets/pencil.png differ
diff --git a/frontend/src/assets/searchicon.png b/frontend/src/assets/searchicon.png
new file mode 100644
index 0000000000..2b41d3fc30
Binary files /dev/null and b/frontend/src/assets/searchicon.png differ
diff --git a/frontend/src/assets/triangle.png b/frontend/src/assets/triangle.png
new file mode 100644
index 0000000000..a5801d1645
Binary files /dev/null and b/frontend/src/assets/triangle.png differ
diff --git a/frontend/src/components/headerinicial/headerinicial.css b/frontend/src/components/headerinicial/headerinicial.css
new file mode 100644
index 0000000000..4d87516c14
--- /dev/null
+++ b/frontend/src/components/headerinicial/headerinicial.css
@@ -0,0 +1,9 @@
+
+
+.headerinicial{
+ width: 100%;
+ height: 85px;
+ background-color: #B92727;
+ display: flex;
+ justify-content: center;
+}
\ No newline at end of file
diff --git a/frontend/src/components/headerinicial/headerinicial.js b/frontend/src/components/headerinicial/headerinicial.js
new file mode 100644
index 0000000000..fa7e97d6e3
--- /dev/null
+++ b/frontend/src/components/headerinicial/headerinicial.js
@@ -0,0 +1,12 @@
+import React from 'react';
+import logo from '../../assets/logo.svg';
+import './headerinicial.css';
+const HeaderInicial = () => {
+ return (
+
+
+
+ );
+}
+
+export default HeaderInicial;
\ No newline at end of file
diff --git a/frontend/src/components/index.js b/frontend/src/components/index.js
new file mode 100644
index 0000000000..abb77dbaa8
--- /dev/null
+++ b/frontend/src/components/index.js
@@ -0,0 +1,3 @@
+import Headerinicial from "./headerinicial/headerinicial";
+
+export { Headerinicial };
\ No newline at end of file
diff --git a/frontend/src/images/buttonimageedit.png b/frontend/src/images/buttonimageedit.png
new file mode 100644
index 0000000000..e830347369
Binary files /dev/null and b/frontend/src/images/buttonimageedit.png differ
diff --git a/frontend/src/images/iconpencil.png b/frontend/src/images/iconpencil.png
new file mode 100644
index 0000000000..dacbdc38da
Binary files /dev/null and b/frontend/src/images/iconpencil.png differ
diff --git a/frontend/src/images/nocoverimage.jpg b/frontend/src/images/nocoverimage.jpg
new file mode 100644
index 0000000000..c82d8e5f55
Binary files /dev/null and b/frontend/src/images/nocoverimage.jpg differ
diff --git a/frontend/src/images/nocoverimage.png b/frontend/src/images/nocoverimage.png
new file mode 100644
index 0000000000..a51cee6b41
Binary files /dev/null and b/frontend/src/images/nocoverimage.png differ
diff --git a/frontend/src/images/noprofileimage.png b/frontend/src/images/noprofileimage.png
new file mode 100644
index 0000000000..0a12965201
Binary files /dev/null and b/frontend/src/images/noprofileimage.png differ
diff --git a/frontend/src/index.js b/frontend/src/index.js
new file mode 100644
index 0000000000..3c8cfcb77d
--- /dev/null
+++ b/frontend/src/index.js
@@ -0,0 +1,136 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import reportWebVitals from './reportWebVitals';
+import {createBrowserRouter, RouterProvider} from "react-router-dom";
+
+import App from './App';
+import Restaurants from './routes/restaurants/Restaurants'
+import RestaurantProfile from './routes/restaurants/RestaurantProfile'
+import RestaurantCreate from './routes/restaurants/RestaurantCreate'
+import RestaurantUpdate from './routes/restaurants/RestaurantUpdate'
+import ErrorPage from './routes/ErrorPage'
+import UserProfile from './routes/users/UserProfile'
+import UserEdit from './routes/users/UserEdit'
+import FollowList from './routes/users/followers/FollowList'
+
+import LandingPage from './routes/landingpage/LandingPage'
+import Login from './routes/login/login'
+import Signup from './routes/signup/Signup'
+
+
+import Feed from './routes/feed/Feed'
+import SearchResult from './routes/search/SearchResult'
+
+import Users from './routes/Users'
+
+import ReviewCreate from './routes/reviews/ReviewCreate'
+import ReviewEdit from './routes/reviews/ReviewEdit'
+import ReviewPage from './routes/reviews/ReviewPage'
+import ReviewsRestaurant from './routes/reviews/ReviewsRestaurant'
+import ReviewsUser from './routes/reviews/ReviewsUser'
+
+const router = createBrowserRouter([
+ {
+ path:"/",
+ element: ,
+ errorElement: ,
+ children: [
+ {
+ path: "/restaurants",
+ element: ,
+ },
+ {
+ path: "/restaurants/create",
+ element:
+ },
+ {
+ path: "/restaurants/update/:id",
+ element:
+ },
+ {
+ path: "/restaurants/:id",
+ element:
+ },
+ {
+ path: "/users/:id",
+ element:
+ },
+ {
+ path: "/users/followers/:id",
+ element:
+ },
+ {
+ path: "/users/following/:id",
+ element:
+ },
+ {
+ path: "/feed",
+ element:
+ },
+ {
+ path: "/search/result",
+ element:
+ },
+ {
+ path: "/users/:id",
+ element: ,
+ },
+ {
+ path: "/users/edit/:id",
+ element:
+ },
+ {
+ path: "/users",
+ element:
+ },
+ {
+ path: "/reviews/:idrest/:iduser/create",
+ element:
+ },
+ {
+ path: "/reviews/:idrest/:iduser/edit",
+ element:
+ },
+ {
+ path: "/reviews/:idrest/:iduser",
+ element:
+ },
+ {
+ path: "/reviews/:idrest/",
+ element:
+ },
+ {
+ path: "/reviews/:iduser",
+ element:
+ },
+ ]
+ },
+ {
+ path:"/login",
+ errorElement: ,
+ element:
+ },
+ {
+ path:"/landingpage",
+ errorElement: ,
+ element:
+ },
+ {
+ path: "/signup",
+ errorElement: ,
+ element:
+ }
+])
+
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+
+
+);
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/frontend/src/logo.svg b/frontend/src/logo.svg
new file mode 100644
index 0000000000..9dfc1c058c
--- /dev/null
+++ b/frontend/src/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/src/reportWebVitals.js b/frontend/src/reportWebVitals.js
new file mode 100644
index 0000000000..5253d3ad9e
--- /dev/null
+++ b/frontend/src/reportWebVitals.js
@@ -0,0 +1,13 @@
+const reportWebVitals = onPerfEntry => {
+ if (onPerfEntry && onPerfEntry instanceof Function) {
+ import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
+ getCLS(onPerfEntry);
+ getFID(onPerfEntry);
+ getFCP(onPerfEntry);
+ getLCP(onPerfEntry);
+ getTTFB(onPerfEntry);
+ });
+ }
+};
+
+export default reportWebVitals;
diff --git a/frontend/src/routes/ErrorPage.js b/frontend/src/routes/ErrorPage.js
new file mode 100644
index 0000000000..f53cc59a7e
--- /dev/null
+++ b/frontend/src/routes/ErrorPage.js
@@ -0,0 +1,42 @@
+import React from "react"
+import "../style/Error.css"
+import Header from "./commons/Header"
+
+const ErrorPage = () => {
+ return (
+