Skip to content

Commit

Permalink
fix: atualizando branch com a main
Browse files Browse the repository at this point in the history
  • Loading branch information
m4rllon committed Aug 12, 2024
2 parents 52cba06 + 60d6e2f commit 03467a7
Show file tree
Hide file tree
Showing 9 changed files with 286 additions and 11 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Django Tests CI

on:
push:
pull_request:
workflow_dispatch:

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.10.12
- name: Install dependencies
run: |
pip install --upgrade pip
pip install -r requirements.txt
- name: Run tests
run: |
cd backend/server
python manage.py test
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Funcionalidades](#funcionalidades)
- [Configuração](#configuração)
- [Uso](#uso)
- [Testes](#testes)
- [Frontend](#frontend)
- [Observações](#observações)
- [Documentação](#documentação)
Expand All @@ -20,7 +21,7 @@

**Licita BSB** é um projeto que visa a divulgação das dispensas de licitação realizadas em Brasília. Através do nosso portal, as dispensas de licitação publicadas nos diários oficiais são disponibilizadas de maneira acessível ao público.

Acesse o nosso portal [aqui](https://bit.ly/licitabsb) para explorar as licitações de forma simples e rápida.
Acesse o nosso portal [aqui](https://licitabsb.netlify.app) para explorar as licitações de forma simples e rápida.

Para aumentar a visibilidade dessas informações, o projeto também inclui um bot na rede social X (antigo Twitter) que compartilha as licitações mais recentes, mantendo a população informada sobre as decisões governamentais.

Expand Down Expand Up @@ -126,6 +127,34 @@ Este bot publica automaticamente as licitações do Diário Oficial do Distrito

2. O bot publicará as licitações no Twitter. Se não houver licitações no dia, o bot publicará uma mensagem informando.

#### Testes

1. Testes Automatizados com Django

O Django oferece um framework robusto para criação e execução de testes automatizados. Abaixo estão as instruções de como rodar os testes.

2. Configuração Inicial

Certifique-se de que os pacotes de teste estão instalados. Se estiver utilizando um ambiente virtual, ative-o antes de instalar as dependências:

```bash
source venv/bin/activate # No Linux/MacOS
venv\Scripts\activate # No Windows
pip install -r requirements.txt
```

3. Estrutura dos Testes

Por convenção, os testes em Django são colocados em um arquivo tests.py dentro de cada aplicação, ou em uma pasta tests/ contendo múltiplos arquivos de teste.

4. Executando os Testes

Para rodar os testes, navegue até `backend/server` e utilize o comando:

```bash
python manage.py test
```

### Frontend

1. Navegue até o diretório `web` e instale as dependências:
Expand Down
4 changes: 2 additions & 2 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ python-dotenv==1.0.1
pytz==2024.1
requests==2.32.3
requests-oauthlib==1.3.1
setuptools==70.3.0
setuptools==72.1.0
six==1.16.0
soupsieve==2.5
sqlparse==0.5.1
tweepy==4.14.0
twitter-text-parser==3.0.0
tzdata==2024.1
urllib3==2.2.2
urllib3==2.2.2
213 changes: 211 additions & 2 deletions backend/server/app/tests.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,212 @@
from django.test import TestCase
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from app.models import Licitacao, Orgao, LicitacaoQuantidade, LicitacaoValoresMensal
from app.serializers import LicitacaoSerializer, LicitacoesQuantidadeMensalSerializer
from django.db.models import Sum, F
from django.db.models.functions import Cast
from django.db.models import FloatField
from datetime import datetime

# Create your tests here.
class Tests(APITestCase):

def setUp(self):
for i in range(15):
Orgao.objects.create(id=i+1, nome=f'Orgao Teste {i+1}')

self.orgao = Orgao.objects.create(id=16, nome='Orgao Teste 16')

for i in range(15):
Licitacao.objects.create(
tipo='Tipo Teste',
data=(datetime.now()).strftime('%d/%m/%Y'),
objeto=f'Objeto Teste {i+1}',
idorgao=self.orgao,
valores=[1000 * (i+1)]
)
# para os endpoints de quantidade mensal e anual
LicitacaoQuantidade.objects.create(ano=2023, mes=1, total_licitacoes=5)
LicitacaoQuantidade.objects.create(ano=2023, mes=2, total_licitacoes=10)
LicitacaoQuantidade.objects.create(ano=2023, mes=3, total_licitacoes=15)
LicitacaoQuantidade.objects.create(ano=2024, mes=1, total_licitacoes=0)
# para os endpoints de valores mensais e anuais
LicitacaoValoresMensal.objects.create(ano=2023, mes=1, valor_total=5000)
LicitacaoValoresMensal.objects.create(ano=2023, mes=2, valor_total=10000)
LicitacaoValoresMensal.objects.create(ano=2023, mes=3, valor_total=15000)
LicitacaoValoresMensal.objects.create(ano=2024, mes=1, valor_total=0)

# TESTE DO ENDPOINT NOME_ORGAOS_POR_ID
def test_nome_orgaos_por_id_valido(self):
# Faz uma requisição GET para a URL nome_orgaos_por_id com o ID do orgao criado
response = self.client.get(reverse('nome_orgaos_por_id', args=[self.orgao.id]))
# Verifica se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Verifica se os dados retornados são os esperados
self.assertEqual(response.data, {'id': self.orgao.id, 'nome': self.orgao.nome})

def test_nome_orgaos_por_id_invalido(self):
# Faz uma requisição GET para a URL nome_orgaos_por_id com um ID inexistente
response = self.client.get(reverse('nome_orgaos_por_id', args=[999]))
# Verifica se o status da resposta é 404 Not Found
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
# Verifica se a mensagem de erro está correta
self.assertEqual(response.data, {'detail': 'Órgão com ID 999 não encontrado.'})

# TESTE DO ENDPOINT LISTAR_ORGAOS
def test_listar_orgaos_paginacao(self):
# Faz uma requisição GET para a URL lista_orgaos
response = self.client.get(reverse('lista_orgaos'))
# Verifica se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Verifica se a página contém 10 itens (página de resultados paginados)
self.assertEqual(len(response.data['results']), 10)

def test_listar_orgaos_busca(self):
# Faz uma requisição GET para a URL lista_orgaos com o parâmetro de busca 'Teste 1'
response = self.client.get(reverse('lista_orgaos'), {'search': 'Teste 1'})
# Verifica se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Verifica se pelo menos um dos resultados contém 'Teste 1' no nome
self.assertTrue(any('Teste 1' in orgao['nome'] for orgao in response.data['results']))

# TESTE DO ENDPOINT LISTAR_LICITACOES
def test_listar_licitacoes_paginacao(self):
response = self.client.get(reverse('listar_licitacoes'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['results']), 10)

def test_listar_licitacoes_filtro_search(self):
response = self.client.get(reverse('listar_licitacoes'), {'search': 'Teste 1'})
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_listar_licitacoes_filtro_orgao(self):
response = self.client.get(reverse('listar_licitacoes'), {'idorgao': self.orgao.id})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertTrue(all(licitacao['idorgao'] == self.orgao.id for licitacao in response.data['results']))

def test_listar_licitacoes_ordenar_por_valor(self):
response = self.client.get(reverse('listar_licitacoes'), {'ordering': 'valores'})
self.assertEqual(response.status_code, status.HTTP_200_OK)
valores = [licitacao['valores'][0] for licitacao in response.data['results'] if licitacao['valores']]
self.assertEqual(valores, sorted(valores))

# TESTE DO ENDPOINT LICITACAO_POR_ID
def test_licitacao_por_id_valido(self):
licitacao = Licitacao.objects.first()
url = reverse('licitacao_por_id', args=[licitacao.id])
response = self.client.get(url)

self.assertEqual(response.status_code, status.HTTP_200_OK)

serializer = LicitacaoSerializer(licitacao)
self.assertEqual(response.data, serializer.data)

def test_licitacao_por_id_invalido(self):
url = reverse('licitacao_por_id', args=[999])
response = self.client.get(url)

self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data, {'detail': 'Licitacao não encontrada'})

# TESTE DO ENDPOINT LISTAR_LICITACOES_QUANTIDADE_MENSAL
def test_listar_licitacoes_quantidade_mensal(self):
# Fazer uma requisição GET para o endpoint
response = self.client.get(reverse('listar_licitacoes_quantidade_mensal'))
# Verificar se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Dados esperados
expected_data = [
{'ano': 2023, 'mes': 1, 'total_licitacoes': 5},
{'ano': 2023, 'mes': 2, 'total_licitacoes': 10},
{'ano': 2023, 'mes': 3, 'total_licitacoes': 15},
{'ano': 2024, 'mes': 1, 'total_licitacoes': 0},
]
# Verificar o conteúdo da resposta
self.assertEqual(response.data, expected_data)

def test_listar_licitacoes_quantidade_mensal_vazio(self):
LicitacaoQuantidade.objects.all().delete()
response = self.client.get(reverse('listar_licitacoes_quantidade_mensal'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [])

# TESTE DO ENDPOINT LISTAR_LICITACOES_QUANTIDADE_ANUAL
def test_listar_licitacoes_quantidade_anual(self):
# Fazer uma requisição GET para o endpoint
response = self.client.get(reverse('listar_licitacoes_quantidade_anual'))
# Verificar se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Dados esperados
expected_data = [
{'ano': 2023, 'total_licitacoes': 30},
{'ano': 2024, 'total_licitacoes': 0}
]
# Verificar o conteúdo da resposta
self.assertEqual(response.data, expected_data)

def test_listar_licitacoes_quantidade_anual_vazio(self):
LicitacaoQuantidade.objects.all().delete()
response = self.client.get(reverse('listar_licitacoes_quantidade_anual'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [])

# TESTE DO ENDPOINT LICITACOES_VALORES_MENSAIS
def test_licitacoes_valores_mensais(self):
response = self.client.get(reverse('licitacoes-valores-mensais'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [
{'ano': 2023, 'mes': 1, 'valor_total': 5000},
{'ano': 2023, 'mes': 2, 'valor_total': 10000},
{'ano': 2023, 'mes': 3, 'valor_total': 15000},
{'ano': 2024, 'mes': 1, 'valor_total': 0}
])

def test_licitacoes_valores_mensais_vazio(self):
LicitacaoValoresMensal.objects.all().delete()
response = self.client.get(reverse('licitacoes-valores-mensais'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [])

# TESTE DO ENDPOINT LICITACOES_VALORES_ANUAIS
def test_licitacoes_valores_anuais(self):
response = self.client.get(reverse('valores-anuais-list'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [
{'ano': 2023, 'valor_total': 30000},
{'ano': 2024, 'valor_total': 0}
])

def test_licitacoes_valores_anuais_vazio(self):
LicitacaoValoresMensal.objects.all().delete()
response = self.client.get(reverse('valores-anuais-list'))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [])

# TESTE DO ENDPOINT LICITACAO_MAIOR_VALOR
def test_licitacao_maior_valor(self):
# Fazer uma requisição GET para o endpoint
response = self.client.get(reverse('licitacao_maior_valor'))

# Verificar se o status da resposta é 200 OK
self.assertEqual(response.status_code, status.HTTP_200_OK)

# Verificar se a licitação retornada é a com maior valor somado
licitacao_com_maior_valor = Licitacao.objects.annotate(
total_valor=Sum(Cast(F('valores'), FloatField()))
).order_by('-total_valor').first()

serializer = LicitacaoSerializer(licitacao_com_maior_valor)
self.assertEqual(response.data, serializer.data)

def test_licitacao_maior_valor_sem_licitacoes(self):
# Remover todas as licitações
Licitacao.objects.all().delete()

# Fazer uma requisição GET para o endpoint
response = self.client.get(reverse('licitacao_maior_valor'))

# Verificar se o status da resposta é 404 NOT FOUND
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)

# Verificar se a mensagem de erro está correta
self.assertEqual(response.data, {'detail': 'Nenhuma licitação encontrada.'})
5 changes: 3 additions & 2 deletions backend/server/app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,8 @@ def licitacao_maior_valor(request):
return Response({'detail': 'Nenhuma licitação encontrada.'}, status=status.HTTP_404_NOT_FOUND)

ALLOWED_ORIGINS = [
'https://fastidious-daffodil-724e94.netlify.app'
'https://fastidious-daffodil-724e94.netlify.app',
'https://licitabsb.netlify.app'
]

@swagger_auto_schema(
Expand Down Expand Up @@ -283,4 +284,4 @@ def subscribe_email(request):
return Response(f"Subscription failed: {response.text}", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
except requests.exceptions.RequestException as e:
print(f"Error subscribing: {e}")
return Response("Subscription failed.", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response("Subscription failed.", status=status.HTTP_500_INTERNAL_SERVER_ERROR)
2 changes: 1 addition & 1 deletion backend/server/server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@

CORS_ALLOWED_ORIGINS = [
"http://localhost:5173",
"https://66b17066871fef417c49ecee--fastidious-daffodil-724e94.netlify.app",
"https://licitabsb.netlify.app",
"https://fastidious-daffodil-724e94.netlify.app",
]

Expand Down
2 changes: 2 additions & 0 deletions backend/server/static/solve.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
apenas para a resolver o warning do test
"C:\Users\thale\OneDrive\Área de Trabalho\unb\trabalho MDS\LicitaBSB-24.1\venv\Lib\site-packages\django\core\handlers\base.py:61: UserWarning: No directory at: C:\Users\thale\OneDrive\Área de Trabalho\unb\trabalho MDS\LicitaBSB-24.1\backend\server\static\"
1 change: 0 additions & 1 deletion backend/testes.py

This file was deleted.

14 changes: 12 additions & 2 deletions backend/twitter/auto.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def encurtar_url(url):
print(f"Exceção ao encurtar URL: {e}")
return url

site = "https://bit.ly/licitabsb" # Link para o site
site = "https://bit.ly/LicitaBSB" # Link para o site

def editar_mensagem(mensagem):
result = parse_tweet(mensagem).asdict()
Expand Down Expand Up @@ -209,9 +209,19 @@ def dividir_palavras_longas(palavras, max_len=48):
# Fechar a conexão com o banco de dados
connection.close()

data_atual = datetime.now().strftime('%d/%m/%Y')

mensagens_alternativas = [
f'Nenhuma nova licitação disponível nas últimas 24 horas (até {data_atual}).\n\nFique ligado: {site}',
f'Estamos de olho! Até {data_atual}, não houve novas licitações.\n\nAcesse: {site}',
f'Sem novidades nas últimas 24 horas (até {data_atual}).\n\nConfira mais detalhes em nosso site: {site}',
]

verificador_de_licitacao = False
if not licitacoes:
mensagens = [f'Nas últimas 24 horas não houve nenhum tipo de licitação liberada no Diário Oficial da União\n\nVisite nosso site: {site}']
# mensagens = [f'Nas últimas 24 horas não houve nenhum tipo de licitação liberada no Diário Oficial da União\n\nVisite nosso site: {site}']
mensagens = [mensagens_alternativas[datetime.now().day % len(mensagens_alternativas)]]
# nao pode postar mensagem duplicada com a biblioteca, entao pega uma mensagem alternativa a cada dia, se nao tiver licitacao
verificador_de_licitacao = True
else:
mensagens = []
Expand Down

0 comments on commit 03467a7

Please sign in to comment.