diff --git a/.env.example b/.env.example
index a4ec410..aa080b4 100755
--- a/.env.example
+++ b/.env.example
@@ -1,7 +1,5 @@
-# Environment variables for PyNews Server
-
-# API Configuration
PYTHONPATH=/server
+BASE_URL=http://localhost:8000
# SQLite Database Configuration
SQLITE_PATH=/app/data/pynewsdb.db
diff --git a/.gitignore b/.gitignore
index 3fb254e..13d2c5d 100755
--- a/.gitignore
+++ b/.gitignore
@@ -136,6 +136,7 @@ celerybeat.pid
# Environments
.env
+.env.test
.envrc
.venv
env/
diff --git a/Dockerfile b/Dockerfile
index 05502f0..eec6591 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -62,3 +62,20 @@ COPY tests tests
COPY scripts scripts
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--lifespan", "on"]
+
+
+FROM builder-base AS scanapi-test
+
+WORKDIR $PYSETUP_PATH
+
+RUN poetry install --no-root --no-interaction
+
+WORKDIR $PROJECT_PATH
+
+COPY poetry.lock pyproject.toml ./
+
+COPY app app
+COPY scanapi scanapi
+COPY scanapi.conf ./
+
+CMD ["poetry", "run", "scanapi", "run"]
diff --git a/Makefile b/Makefile
index b02b1d5..3c081f1 100755
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,10 @@ help: ## Mostra esta mensagem de ajuda
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " $(GREEN)%-15s$(NC) %s\n", $$1, $$2}'
+install: ## Instala dependências com Poetry
+ @echo "$(YELLOW)Instalando dependências...$(NC)"
+ poetry install
+
build: ## Constrói as imagens Docker
@echo "$(YELLOW)Construindo imagens Docker...$(NC)"
docker-compose build
@@ -26,6 +30,19 @@ down: ## Para os serviços
logs: ## Mostra os logs dos serviços
docker-compose logs -f pynews-api
+restart: ## Reinicia os serviços
+ @echo "$(YELLOW)Reiniciando serviços...$(NC)"
+ docker-compose restart
+
+dev: build up ## Ambiente de desenvolvimento completo
+ @echo "$(GREEN)Ambiente de desenvolvimento iniciado!$(NC)"
+ @echo "API: http://localhost:8000"
+ @echo "Docs: http://localhost:8000/docs"
+
+prod: ## Inicia em modo produção
+ @echo "$(YELLOW)Iniciando em modo produção...$(NC)"
+ docker-compose -f docker-compose.yaml up -d
+
test: ## Executa os testes
@echo "$(YELLOW)Executando testes...$(NC)"
poetry run pytest
@@ -34,6 +51,12 @@ test-cov: ## Executa os testes com coverage
@echo "$(YELLOW)Executando testes com coverage...$(NC)"
poetry run pytest --cov=app --cov-report=html
+docker-test:
+ docker exec -e PYTHONPATH=/app $(API_CONTAINER_NAME) pytest -s --cov-report=term-missing --cov-report html --cov-report=xml --cov=app tests/
+
+scanapi-test: # Executa testes com scanapi e gera report acessado na porta 8080 no path {url}/scanapi-report.html
+ docker-compose run --rm scanapi-tests
+
lint: ## Verifica o código com ruff
@echo "$(YELLOW)Verificando código...$(NC)"
poetry run ruff check .
@@ -42,24 +65,6 @@ format: ## Formata o código
@echo "$(YELLOW)Formatando código...$(NC)"
poetry run ruff format .
-clean: ## Remove containers, volumes e imagens
- @echo "$(YELLOW)Limpando containers e volumes...$(NC)"
- docker-compose down -v --remove-orphans
- docker system prune -f
-
-dev: build up ## Ambiente de desenvolvimento completo
- @echo "$(GREEN)Ambiente de desenvolvimento iniciado!$(NC)"
- @echo "API: http://localhost:8000"
- @echo "Docs: http://localhost:8000/docs"
-
-prod: ## Inicia em modo produção
- @echo "$(YELLOW)Iniciando em modo produção...$(NC)"
- docker-compose -f docker-compose.yaml up -d
-
-restart: ## Reinicia os serviços
- @echo "$(YELLOW)Reiniciando serviços...$(NC)"
- docker-compose restart
-
health: ## Verifica o health check da API
@echo "$(YELLOW)Verificando saúde da API...$(NC)"
curl -f http://localhost:8000/api/healthcheck || echo "API não está respondendo"
@@ -110,6 +115,11 @@ install: ## Instala dependências com Poetry
shell: ## Entra no shell do container
docker-compose exec pynews-api bash
+clean: ## Remove containers, volumes e imagens
+ @echo "$(YELLOW)Limpando containers e volumes...$(NC)"
+ docker-compose down -v --remove-orphans
+ docker system prune -f
+
setup: install build up ## Setup completo do projeto
@echo "$(GREEN)Setup completo realizado!$(NC)"
@echo "$(GREEN)Acesse: http://localhost:8000/docs$(NC)"
diff --git a/README.md b/README.md
index 182619c..af3a346 100755
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
# pynewsserver
+
Serviço de Noticas e Bibliotecas PyNews
## 💡 Visão Geral
## 💻 Tecnologias Utilizadas
+
- Python
- FastAPI
- Pydantic
@@ -13,15 +15,19 @@ Serviço de Noticas e Bibliotecas PyNews
- ruff (linter)
## 🚀 Recursos e Funcionalidades
+
Endpoints para CRUD de noticias selecionadas pela comunidade.
### Schema da API
+
[Documentação de referencia API Dog](https://apidog.com/apidoc/shared/70418cab-ddba-4c7d-97a4-8b70b43a7946/)
+
---
### Schema do Servidor
+
```
fastapi_news_service/
│
@@ -84,24 +90,28 @@ sequenceDiagram
## ⚙️ Como Rodar
### 📋 Pré-requisitos
+
- Docker e Docker Compose instalados
- Git (para clonar o repositório)
### 🚀 Início Rápido
1. **Clone o repositório:**
+
```bash
git clone
cd PyNewsServer
```
2. **Configure as variáveis de ambiente (opcional):**
+
```bash
cp .env.example .env
# Edite o arquivo .env conforme necessário
```
3. **Inicie o serviço:**
+
```bash
docker-compose up -d
```
@@ -126,6 +136,7 @@ Para mais detalhes sobre configuração do SQLite, consulte: [docs/sqlite-setup.
### ▶️ Guia de Execução para Desenvolvimento
#### Usando Docker (Recomendado)
+
```bash
# Construir e iniciar em modo desenvolvimento
docker-compose up --build
@@ -138,6 +149,7 @@ docker-compose down
```
#### Usando Poetry (Local)
+
```bash
# Instalar dependências
poetry install
@@ -179,42 +191,68 @@ docker-compose up -d --force-recreate
### 🔧 Comandos Úteis
#### Usando Makefile (Recomendado)
+
```bash
# Ver todos os comandos disponíveis
make help
-# Setup completo do projeto
-make setup
+# Instalar dependências com Poetry
+make install
-# Ambiente de desenvolvimento
-make dev
+# Setup completo do projeto (instala, constrói e sobe containers)
+make setup
-# Construir e iniciar
+# Construir imagens Docker
make build
+
+# Iniciar serviços
make up
-# Ver logs
+# Parar serviços
+make down
+
+# Reiniciar serviços
+make restart
+
+# Ver logs da API
make logs
-# Executar testes
+# Acessar shell dentro do container da API
+make shell
+
+# Executar testes locais
make test
+
+# Executar testes com coverage
make test-cov
-# Linting e formatação
+# Executar testes dentro do container Docker
+make docker-test
+
+# Executar testes com ScanAPI e gerar report que pode ser acessado na porta 8080 no path {url}/scanapi-report.html
+make scanapi-test
+
+# Verificar código com Ruff
make lint
+
+# Formatar código com Ruff
make format
# Verificar saúde da API
make health
-# Parar serviços
-make down
+# Ambiente de desenvolvimento completo
+make dev
-# Limpeza completa
+# Iniciar em modo produção
+make prod
+
+# Limpeza completa de containers, volumes e imagens
make clean
```
#### Comandos Docker Diretos
+
```bash
# Entrar no container
docker-compose exec pynews-api bash
@@ -232,6 +270,7 @@ docker-compose down -v
### 🛠️ Desenvolvimento
#### Estrutura de Testes
+
```bash
# Rodar todos os testes
poetry run pytest
@@ -243,7 +282,12 @@ poetry run pytest --cov=app
poetry run pytest tests/test_auth.py
```
+##### Requisitos para utilizar [ScanAPI](https://scanapi.dev/)
+
+- Criar .env.test seguindo o exemplo em .env.example com as credenciais necessárias
+
#### Linting e Formatação
+
```bash
# Verificar código
poetry run ruff check .
@@ -255,6 +299,6 @@ poetry run ruff format .
poetry run ruff check . --fix
```
-
## referencias
+
[Opinion based fastapi best practices](https://github.com/zhanymkanov/fastapi-best-practices)
diff --git a/app/routers/admin/routes.py b/app/routers/admin/routes.py
index 157b06b..c67e85f 100644
--- a/app/routers/admin/routes.py
+++ b/app/routers/admin/routes.py
@@ -16,9 +16,9 @@
async def create_admin(session: AsyncSession):
- ADMIN_USER = os.getenv("ADMIN_USER")
- ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD")
- ADMIN_EMAIL = os.getenv("ADMIN_EMAIL")
+ ADMIN_USER = os.getenv("ADMIN_USER", "")
+ ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "")
+ ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "")
password = ADMIN_PASSWORD
hashed_password = auth.hash_password(password)
community = DBCommunity(
diff --git a/app/routers/libraries/routes.py b/app/routers/libraries/routes.py
index f18cb3f..259a08a 100644
--- a/app/routers/libraries/routes.py
+++ b/app/routers/libraries/routes.py
@@ -30,6 +30,10 @@ class SubscribeLibraryResponse(BaseModel):
status: str = "Subscribed in libraries successfully"
+class LibraryRequestResponse(BaseModel):
+ status: str = "Library requested successfully"
+
+
def setup():
router = APIRouter(prefix="/libraries", tags=["libraries"])
@@ -163,7 +167,7 @@ async def subscribe_libraries(
@router.post(
"/request",
- response_model=LibraryResponse,
+ response_model=LibraryRequestResponse,
status_code=status.HTTP_200_OK,
summary="Request a library",
description="Request a library to follow",
@@ -189,7 +193,7 @@ async def request_library(
library_request, request.app.db_session_factory
)
- return LibraryResponse()
+ return LibraryRequestResponse()
except HTTPException as e:
raise e
except Exception as e:
diff --git a/docker-compose.yaml b/docker-compose.yaml
index 48cc04d..e9dcf78 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -40,7 +40,35 @@ services:
"
restart: "no"
+ scanapi-tests:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ target: scanapi-test
+ container_name: scanapi-tests
+ env_file:
+ - .env
+ environment:
+ - BASE_URL=http://pynews-server:8000
+ volumes:
+ - report-data:/server/scanapi
+ depends_on:
+ pynews-api:
+ condition: service_healthy
+ command: poetry run scanapi run
+
+ scanapi-report-viewer:
+ image: nginx:alpine
+ container_name: scanapi-report-viewer
+ ports:
+ - "8080:80"
+ volumes:
+ - report-data:/usr/share/nginx/html:ro
+ depends_on:
+ - scanapi-tests
+
volumes:
+ report-data:
sqlite_data:
driver: local
driver_opts:
diff --git a/poetry.lock b/poetry.lock
old mode 100644
new mode 100755
index 2205579..f86a744
--- a/poetry.lock
+++ b/poetry.lock
@@ -1,4 +1,4 @@
-# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
+# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand.
[[package]]
name = "aiosqlite"
@@ -51,6 +51,18 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""}
[package.extras]
trio = ["trio (>=0.31.0)"]
+[[package]]
+name = "appdirs"
+version = "1.4.4"
+description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+optional = false
+python-versions = "*"
+groups = ["main"]
+files = [
+ {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
+ {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
+]
+
[[package]]
name = "bcrypt"
version = "4.3.0"
@@ -483,19 +495,27 @@ files = [
[[package]]
name = "cucumber-tag-expressions"
-version = "6.2.0"
+version = "8.0.0"
description = "Provides a tag-expression parser and evaluation logic for cucumber/behave"
optional = false
-python-versions = ">=2.7"
+python-versions = ">=3.10"
groups = ["dev"]
files = [
- {file = "cucumber_tag_expressions-6.2.0-py2.py3-none-any.whl", hash = "sha256:f94404b656831c56a3815da5305ac097003884d2ae64fa51f5f4fad82d97e583"},
- {file = "cucumber_tag_expressions-6.2.0.tar.gz", hash = "sha256:b60aa2cdbf9ac43e28d9b0e4fd49edf9f09d5d941257d2912f5228f9d166c023"},
+ {file = "cucumber_tag_expressions-8.0.0-py3-none-any.whl", hash = "sha256:bfe552226f62a4462ee91c9643582f524af84ac84952643fb09057580cbb110a"},
+ {file = "cucumber_tag_expressions-8.0.0.tar.gz", hash = "sha256:4af80282ff0349918c332428176089094019af6e2a381a2fd8f1c62a7a6bb7e8"},
]
-[package.extras]
-develop = ["backports.shutil_which ; python_version <= \"3.3\"", "build (>=0.5.1)", "coverage", "invoke (>=1.7.3)", "path (>=13.1.0) ; python_version >= \"3.5\"", "path.py (>=11.5.0) ; python_version < \"3.5\"", "pathlib ; python_version <= \"3.4\"", "pycmd", "pylint", "pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-html (>=1.19.0)", "ruff", "setuptools", "setuptools-scm", "six (>=1.16.0)", "tox (>=4.26,<4.27)", "twine (>=1.13.0)", "wheel"]
-testing = ["PyYAML (>=5.4.1)", "pathlib ; python_version <= \"3.4\"", "pytest (<5.0) ; python_version < \"3.0\"", "pytest (>=5.0) ; python_version >= \"3.0\"", "pytest-html (>=1.19.0)"]
+[[package]]
+name = "curlify2"
+version = "1.0.3.1"
+description = "Library to convert python requests and httpx object to curl command."
+optional = false
+python-versions = ">=3.7,<4.0"
+groups = ["main"]
+files = [
+ {file = "curlify2-1.0.3.1-py3-none-any.whl", hash = "sha256:59018ef55c4458d1a79565bdcee44f53f9e565d920d84ec16daf6a61bddd6d37"},
+ {file = "curlify2-1.0.3.1.tar.gz", hash = "sha256:cebec254a0e4fbe3945a4abe93639ebe622ae6957e70f683b18879db2ab48703"},
+]
[[package]]
name = "deprecated"
@@ -699,14 +719,14 @@ trio = ["trio (>=0.22.0,<1.0)"]
[[package]]
name = "httpx"
-version = "0.28.1"
+version = "0.27.2"
description = "The next generation HTTP client."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
- {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
- {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
+ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"},
+ {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"},
]
[package.dependencies]
@@ -714,6 +734,7 @@ anyio = "*"
certifi = "*"
httpcore = "==1.*"
idna = "*"
+sniffio = "*"
[package.extras]
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
@@ -764,6 +785,24 @@ files = [
{file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
]
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+ {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
[[package]]
name = "limits"
version = "5.6.0"
@@ -792,6 +831,102 @@ redis = ["redis (>3,!=4.5.2,!=4.5.3,<7.0.0)"]
rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"]
valkey = ["valkey (>=6)"]
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = false
+python-versions = ">=3.10"
+groups = ["main"]
+files = [
+ {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
+ {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "markdown-it-pyrs", "mistletoe (>=1.0,<2.0)", "mistune (>=3.0,<4.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins (>=0.5.0)"]
+profiling = ["gprof2dot"]
+rtd = ["ipykernel", "jupyter_sphinx", "mdit-py-plugins (>=0.5.0)", "myst-parser", "pyyaml", "sphinx", "sphinx-book-theme (>=1.0,<2.0)", "sphinx-copybutton", "sphinx-design"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions", "requests"]
+
+[[package]]
+name = "markupsafe"
+version = "2.1.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
+ {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
+ {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
+ {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
+ {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
+ {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
+ {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = false
+python-versions = ">=3.7"
+groups = ["main"]
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
[[package]]
name = "mslex"
version = "1.3.0"
@@ -912,14 +1047,14 @@ files = [
[[package]]
name = "packaging"
-version = "25.0"
+version = "24.2"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
groups = ["main", "dev"]
files = [
- {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
- {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
+ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+ {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
]
[[package]]
@@ -1191,7 +1326,7 @@ version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python."
optional = false
python-versions = ">=3.8"
-groups = ["dev"]
+groups = ["main", "dev"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
@@ -1392,6 +1527,25 @@ files = [
{file = "pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f"},
]
+[[package]]
+name = "rich"
+version = "14.0.0"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+optional = false
+python-versions = ">=3.8.0"
+groups = ["main"]
+files = [
+ {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"},
+ {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.2.0"
+pygments = ">=2.13.0,<3.0.0"
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<9)"]
+
[[package]]
name = "ruff"
version = "0.6.9"
@@ -1420,6 +1574,29 @@ files = [
{file = "ruff-0.6.9.tar.gz", hash = "sha256:b076ef717a8e5bc819514ee1d602bbdca5b4420ae13a9cf61a0c0a4f53a2baa2"},
]
+[[package]]
+name = "scanapi"
+version = "2.12.0"
+description = "Automated Testing and Documentation for your REST API"
+optional = false
+python-versions = "<4.0,>=3.10"
+groups = ["main"]
+files = [
+ {file = "scanapi-2.12.0-py3-none-any.whl", hash = "sha256:5aa0e644c2037965c4c0c1db884bf4fc02078acb51c065700785de25557b5399"},
+ {file = "scanapi-2.12.0.tar.gz", hash = "sha256:fd488a9f9eaed51c0999c7f2d54649507786172fbb053b347b4e4c465937f1d8"},
+]
+
+[package.dependencies]
+appdirs = ">=1.4.4,<2.0.0"
+click = ">=8.1.3"
+curlify2 = ">=1.0.1,<2.0.0"
+httpx = ">=0.27.0,<0.28.0"
+Jinja2 = ">=3.1.0,<3.2.0"
+MarkupSafe = "2.1.2"
+packaging = ">=24.0,<25.0"
+PyYAML = ">=6.0.2,<6.1.0"
+rich = "14.0.0"
+
[[package]]
name = "six"
version = "1.17.0"
@@ -1825,4 +2002,4 @@ files = [
[metadata]
lock-version = "2.1"
python-versions = "^3.12"
-content-hash = "112d46cef288dccf9f24dc2619f239731fb75f3873640335ce0dc78126921a25"
+content-hash = "1e925f5e9e4e5f11abd63f69a63eb75da5fa567a86ef668c0824c4b88424aca6"
diff --git a/pyproject.toml b/pyproject.toml
index 9d92249..38e5b3a 100755
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,7 @@ python = "^3.12"
fastapi = "^0.115.8"
orjson = "^3.10.15"
uvicorn = "^0.22.0"
-httpx = "^0.28.1"
+httpx = "^0.27.0"
sqlmodel = "^0.0.24"
aiosqlite = "^0.21.0"
pre-commit = "^4.2.0"
@@ -22,6 +22,7 @@ pyjwt = "^2.10.1"
bcrypt = "^4.3.0"
cryptography = "^45.0.7"
slowapi = "^0.1.9"
+scanapi = "^2.12.0"
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.2"
diff --git a/scanapi.conf b/scanapi.conf
new file mode 100644
index 0000000..b037963
--- /dev/null
+++ b/scanapi.conf
@@ -0,0 +1,12 @@
+project_name: PyNewsServer
+spec_path: scanapi/scanapi.yaml
+output_path: scanapi/scanapi-report.html
+report:
+ hide_request:
+ headers:
+ - Authorization
+ body:
+ - password
+ hide_response:
+ body:
+ - access_token
\ No newline at end of file
diff --git a/scanapi/libraries_authenticated.yaml b/scanapi/libraries_authenticated.yaml
new file mode 100644
index 0000000..27804dc
--- /dev/null
+++ b/scanapi/libraries_authenticated.yaml
@@ -0,0 +1,48 @@
+name: libraries_authenticated
+path: libraries
+headers:
+ Authorization: Bearer ${access_token}
+ user-email: teste@teste.com
+requests:
+ - name: get_libraries
+ params:
+ language: Python
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - !include tests/body_is_array.yaml
+ - name: create_library
+ method: POST
+ body:
+ library_name: "Flask"
+ news:
+ - tag: "updates"
+ description: "News about Python"
+ logo: "https://example.com/python-logo.png"
+ version: "1.0.0"
+ release_date: "2023-01-01"
+ releases_doc_url: "https://example.com/python-release-notes.txt"
+ fixed_release_url: "https://example.com/python-fixed-release.txt"
+ language: "Python"
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - !include tests/libraries/body_status_created.yaml
+ - name: subscribe_to_library
+ path: subscribe
+ method: POST
+ body:
+ tags:
+ - updates
+ libraries_list:
+ - Flask
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - !include tests/libraries/body_status_subscribed.yaml
+ - name: request_library_news
+ path: request
+ method: POST
+ body:
+ library_name: "Flask"
+ library_home_page: "https://flask.palletsprojects.com/en/2.3.x/"
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - !include tests/libraries/body_status_requested.yaml
diff --git a/scanapi/libraries_unauthenticated.yaml b/scanapi/libraries_unauthenticated.yaml
new file mode 100644
index 0000000..a02f29f
--- /dev/null
+++ b/scanapi/libraries_unauthenticated.yaml
@@ -0,0 +1,45 @@
+name: libraries_unauthenticated
+path: libraries
+requests:
+ - name: get_libraries
+ params:
+ language: Python
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: create_library
+ method: POST
+ body:
+ library_name: "Python"
+ news:
+ - tag: "updates"
+ description: "News about Python"
+ logo: "https://example.com/python-logo.png"
+ version: "1.0.0"
+ release_date: "2023-01-01"
+ releases_doc_url: "https://example.com/python-release-notes.txt"
+ fixed_release_url: "https://example.com/python-fixed-release.txt"
+ language: "Python"
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: subscribe_to_library
+ path: subscribe
+ method: POST
+ body:
+ tags:
+ - updates
+ libraries_list:
+ - Flask
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: request_library_news
+ path: request
+ method: POST
+ body:
+ library_name: "Flask"
+ library_home_page: "https://flask.palletsprojects.com/en/2.3.x/"
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
diff --git a/scanapi/news_authenticated.yaml b/scanapi/news_authenticated.yaml
new file mode 100644
index 0000000..1250675
--- /dev/null
+++ b/scanapi/news_authenticated.yaml
@@ -0,0 +1,36 @@
+name: news_authenticated
+path: news
+headers:
+ Authorization: Bearer ${access_token}
+ user-email: teste@teste.com
+requests:
+ - name: create_news
+ method: POST
+ body:
+ title: "Test News"
+ content: "This is a test news article."
+ category: "test_category"
+ tags: "test_tag"
+ source_url: "https://example.com/test-news"
+ social_media_url: "https://example.com/testnews"
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - name: get_news
+ vars:
+ news_id: ${{ response.json()["news_list"][0]["id"] }}
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - name: like_news
+ path: ${news_id}/like
+ method: POST
+ body:
+ total_likes: 1
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - name: dislike_news
+ path: ${news_id}/like
+ method: DELETE
+ body:
+ total_likes: 1
+ tests:
+ - !include tests/status_code_is_200.yaml
diff --git a/scanapi/news_unauthenticated.yaml b/scanapi/news_unauthenticated.yaml
new file mode 100644
index 0000000..ae2bb4d
--- /dev/null
+++ b/scanapi/news_unauthenticated.yaml
@@ -0,0 +1,37 @@
+name: news_unauthenticated
+path: news
+vars:
+ news_id: "1"
+requests:
+ - name: get_news
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: create_news
+ method: POST
+ body:
+ title: "Test News"
+ content: "This is a test news article."
+ category: "test_category"
+ tags: "test_tag"
+ source_url: "https://example.com/test-news"
+ social_media_url: "https://example.com/testnews"
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: like_news
+ path: ${news_id}/like
+ method: POST
+ body:
+ total_likes: 1
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
+ - name: dislike_news
+ path: ${news_id}/like
+ method: DELETE
+ body:
+ total_likes: 1
+ tests:
+ - !include tests/status_code_is_401.yaml
+ - !include tests/authentication/body_detail_not_authenticated.yaml
diff --git a/scanapi/scanapi-report.html b/scanapi/scanapi-report.html
new file mode 100644
index 0000000..ec0fcd4
--- /dev/null
+++ b/scanapi/scanapi-report.html
@@ -0,0 +1,5302 @@
+
+
+
+
+
+
+
+ ScanAPI Report
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GET
+ healthcheck
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/healthcheck
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X GET -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -d 'b''' http://localhost:8000/api/healthcheck --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.012535 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:51 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 38
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"healthy","version":"2.0.0"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::healthcheck::status_code_is_200
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::healthcheck::body_status_healthy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ create_community
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/authentication/create_community
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 26
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 26" -H "content-type: application/json" -d '{"name": "Test Community"}' http://localhost:8000/api/authentication/create_community --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.277971 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:51 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 18
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"msg":"succes? "}
+
+ ❏
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ login_for_access_token
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/authentication/token
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-type
+
+
+ application/x-www-form-urlencoded
+
+
+
+
+ content-length
+
+
+ 42
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-type: application/x-www-form-urlencoded" -H "content-length: 42" -d 'username=username&password=123Asd%21%40%23' http://localhost:8000/api/authentication/token --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.299019 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:52 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 187
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"access_token": "SENSITIVE_INFORMATION", "token_type": "Bearer", "expires_in": 1200}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::login_for_access_token::status_code_is_200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GET
+ get_libraries
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries?language=Python
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X GET -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -d 'b''' http://localhost:8000/api/libraries?language=Python --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.012075 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:52 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 2
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ []
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::get_libraries::status_code_is_200
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::get_libraries::body_is_array
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ create_library
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 347
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 347" -H "content-type: application/json" -d '{"library_name": "Flask", "news": [{"tag": "updates", "description": "News about Python"}], "logo": "https://example.com/python-logo.png", "version": "1.0.0", "release_date": "2023-01-01", "releases_doc_url": "https://example.com/python-release-notes.txt", "fixed_release_url": "https://example.com/python-fixed-release.txt", "language": "Python"}' http://localhost:8000/api/libraries --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.016772 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:53 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 41
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"Library created successfully"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::create_library::status_code_is_200
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::create_library::body_status_created
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ subscribe_to_library
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries/subscribe
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 50
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 50" -H "content-type: application/json" -d '{"tags": ["updates"], "libraries_list": ["Flask"]}' http://localhost:8000/api/libraries/subscribe --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.019481 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:53 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 49
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"Subscribed in libraries successfully"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::subscribe_to_library::status_code_is_200
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::subscribe_to_library::body_status_subscribed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ request_library_news
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries/request
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 93
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 93" -H "content-type: application/json" -d '{"library_name": "Flask", "library_home_page": "https://flask.palletsprojects.com/en/2.3.x/"}' http://localhost:8000/api/libraries/request --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.014056 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:53 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 43
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"Library requested successfully"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::request_library_news::status_code_is_200
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_authenticated::request_library_news::body_status_requested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GET
+ get_libraries
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries?language=Python
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X GET -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -d 'b''' http://localhost:8000/api/libraries?language=Python --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.005129 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:54 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::get_libraries::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::get_libraries::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ create_library
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 348
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 348" -H "content-type: application/json" -d '{"library_name": "Python", "news": [{"tag": "updates", "description": "News about Python"}], "logo": "https://example.com/python-logo.png", "version": "1.0.0", "release_date": "2023-01-01", "releases_doc_url": "https://example.com/python-release-notes.txt", "fixed_release_url": "https://example.com/python-fixed-release.txt", "language": "Python"}' http://localhost:8000/api/libraries --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.005741 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:54 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::create_library::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::create_library::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ subscribe_to_library
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries/subscribe
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 50
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 50" -H "content-type: application/json" -d '{"tags": ["updates"], "libraries_list": ["Flask"]}' http://localhost:8000/api/libraries/subscribe --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.005629 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:54 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::subscribe_to_library::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::subscribe_to_library::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ request_library_news
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/libraries/request
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 93
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 93" -H "content-type: application/json" -d '{"library_name": "Flask", "library_home_page": "https://flask.palletsprojects.com/en/2.3.x/"}' http://localhost:8000/api/libraries/request --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.004986 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:56 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::request_library_news::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::libraries_unauthenticated::request_library_news::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ create_news
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 213
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 213" -H "content-type: application/json" -d '{"title": "Test News", "content": "This is a test news article.", "category": "test_category", "tags": "test_tag", "source_url": "https://example.com/test-news", "social_media_url": "https://example.com/testnews"}' http://localhost:8000/api/news --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.015065 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:56 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 24
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"News Criada"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_authenticated::create_news::status_code_is_200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GET
+ get_news
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X GET -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -d 'b''' http://localhost:8000/api/news --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.009937 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:56 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 425
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"status":"Lista de News Obtida","news_list":[{"content":"This is a test news article.","title":"Test News","category":"test_category","source_url":"https://example.com/test-news","user_email_list":"[]","likes":0,"created_at":"2025-10-09T01:02:56.548455","id":1,"user_email":"teste@teste.com","tags":"test_tag","social_media_url":"https://example.com/testnews","community_id":null,"updated_at":"2025-10-09T01:02:56.548593"}]}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_authenticated::get_news::status_code_is_200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ like_news
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news/1/like
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 18
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 18" -H "content-type: application/json" -d '{"total_likes": 1}' http://localhost:8000/api/news/1/like --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.014314 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:57 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 17
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"total_likes":1}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_authenticated::like_news::status_code_is_200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DELETE
+ dislike_news
+
+
+ 200
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news/1/like
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ authorization
+
+
+ SENSITIVE_INFORMATION
+
+
+
+
+ user-email
+
+
+ teste@teste.com
+
+
+
+
+ content-length
+
+
+ 18
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X DELETE -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "authorization: SENSITIVE_INFORMATION" -H "user-email: teste@teste.com" -H "content-length: 18" -H "content-type: application/json" -d '{"total_likes": 1}' http://localhost:8000/api/news/1/like --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 200
+
+
+
+
+ response time
+
+
+ 0.012571 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:57 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ content-length
+
+
+ 17
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"total_likes":0}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_authenticated::dislike_news::status_code_is_200
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ GET
+ get_news
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X GET -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -d 'b''' http://localhost:8000/api/news --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.005016 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:58 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::get_news::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::get_news::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ create_news
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 213
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 213" -H "content-type: application/json" -d '{"title": "Test News", "content": "This is a test news article.", "category": "test_category", "tags": "test_tag", "source_url": "https://example.com/test-news", "social_media_url": "https://example.com/testnews"}' http://localhost:8000/api/news --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.0052 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:58 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::create_news::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::create_news::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ POST
+ like_news
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news/1/like
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 18
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X POST -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 18" -H "content-type: application/json" -d '{"total_likes": 1}' http://localhost:8000/api/news/1/like --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.004478 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:58 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::like_news::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::like_news::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ DELETE
+ dislike_news
+
+
+ 401
+
+ P
+
+
+
+
+
+
+ Request
+ Full URL: http://localhost:8000/api/news/1/like
+
+
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+
+ host
+
+
+ localhost:8000
+
+
+
+
+ accept
+
+
+ */*
+
+
+
+
+ accept-encoding
+
+
+ gzip, deflate
+
+
+
+
+ connection
+
+
+ keep-alive
+
+
+
+
+ user-agent
+
+
+ python-httpx/0.27.2
+
+
+
+
+ content-length
+
+
+ 18
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+
+
+ cURL
+ curl -X DELETE -H "host: localhost:8000" -H "accept: */*" -H "accept-encoding: gzip, deflate" -H "connection: keep-alive" -H "user-agent: python-httpx/0.27.2" -H "content-length: 18" -H "content-type: application/json" -d '{"total_likes": 1}' http://localhost:8000/api/news/1/like --compressed
+ ❏
+
+
+
+
+ Response
+
+
+
+
+ status code
+
+
+ 401
+
+
+
+
+ response time
+
+
+ 0.017986 s
+
+
+
+
+ redirect
+
+
+ False
+
+
+
+
+
+
+
+ Headers
+
+
+
+
+ Header
+
+
+ Value
+
+
+
+
+
+
+ date
+
+
+ Thu, 09 Oct 2025 01:02:59 GMT
+
+
+
+
+
+
+ server
+
+
+ uvicorn
+
+
+
+
+
+
+ www-authenticate
+
+
+ Bearer
+
+
+
+
+
+
+ content-length
+
+
+ 30
+
+
+
+
+
+
+ content-type
+
+
+ application/json
+
+
+
+
+
+
+
+
+ Content
+
+ {"detail":"Not authenticated"}
+
+ ❏
+
+
+
+
+
+ Tests
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::dislike_news::status_code_is_401
+
+
+
+
+
+
+
+
+ [PASSED]
+
+
+
+
+ py_news_server::news_unauthenticated::dislike_news::body_detail_not_authenticated
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tests Summary
+
+ PASSED: 31
+ FAILURES: 0
+ ERRORS: 0
+ Total Time: 0:00:07.858519
+
+
+
+
+
+
+
+ Generated by ScanAPI 2.12.0
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scanapi/scanapi.yaml b/scanapi/scanapi.yaml
new file mode 100644
index 0000000..79e3994
--- /dev/null
+++ b/scanapi/scanapi.yaml
@@ -0,0 +1,29 @@
+endpoints:
+ - name: py_news_server
+ path: ${BASE_URL}/api
+ vars:
+ admin_user: ${ADMIN_USER}
+ admin_password: ${ADMIN_PASSWORD}
+ requests:
+ - name: healthcheck
+ path: healthcheck
+ tests:
+ - !include tests/status_code_is_200.yaml
+ - !include tests/healthcheck/body_status_healthy.yaml
+ - name: login_for_access_token
+ path: authentication/token
+ method: POST
+ headers:
+ Content-Type: application/x-www-form-urlencoded
+ body:
+ username: ${admin_user}
+ password: ${admin_password}
+ tests:
+ - !include tests/status_code_is_200.yaml
+ vars:
+ access_token: ${{ response.json()["access_token"] if response.status_code == 200 else "" }}
+ endpoints:
+ - !include libraries_authenticated.yaml
+ - !include libraries_unauthenticated.yaml
+ - !include news_authenticated.yaml
+ - !include news_unauthenticated.yaml
diff --git a/scanapi/tests/authentication/body_detail_not_authenticated.yaml b/scanapi/tests/authentication/body_detail_not_authenticated.yaml
new file mode 100644
index 0000000..c0b6a60
--- /dev/null
+++ b/scanapi/tests/authentication/body_detail_not_authenticated.yaml
@@ -0,0 +1,2 @@
+name: body_detail_not_authenticated
+assert: ${{ response.json()["detail"] == "Not authenticated" }}
diff --git a/scanapi/tests/body_is_array.yaml b/scanapi/tests/body_is_array.yaml
new file mode 100644
index 0000000..1b7462a
--- /dev/null
+++ b/scanapi/tests/body_is_array.yaml
@@ -0,0 +1,2 @@
+name: body_is_array
+assert: ${{ len(response.json()) >= 0 }}
diff --git a/scanapi/tests/healthcheck/body_status_healthy.yaml b/scanapi/tests/healthcheck/body_status_healthy.yaml
new file mode 100644
index 0000000..cf3ccac
--- /dev/null
+++ b/scanapi/tests/healthcheck/body_status_healthy.yaml
@@ -0,0 +1,2 @@
+name: body_status_healthy
+assert: ${{ response.json()["status"] == "healthy" and response.json()["version"] == "2.0.0" }}
diff --git a/scanapi/tests/libraries/body_status_created.yaml b/scanapi/tests/libraries/body_status_created.yaml
new file mode 100644
index 0000000..48e3c33
--- /dev/null
+++ b/scanapi/tests/libraries/body_status_created.yaml
@@ -0,0 +1,2 @@
+name: body_status_created
+assert: ${{ response.json()["status"] == "Library created successfully" }}
diff --git a/scanapi/tests/libraries/body_status_requested.yaml b/scanapi/tests/libraries/body_status_requested.yaml
new file mode 100644
index 0000000..1aeed88
--- /dev/null
+++ b/scanapi/tests/libraries/body_status_requested.yaml
@@ -0,0 +1,2 @@
+name: body_status_requested
+assert: ${{ response.json()["status"] == "Library requested successfully" }}
diff --git a/scanapi/tests/libraries/body_status_subscribed.yaml b/scanapi/tests/libraries/body_status_subscribed.yaml
new file mode 100644
index 0000000..d87e542
--- /dev/null
+++ b/scanapi/tests/libraries/body_status_subscribed.yaml
@@ -0,0 +1,2 @@
+name: body_status_subscribed
+assert: ${{ response.json()["status"] == "Subscribed in libraries successfully" }}
diff --git a/scanapi/tests/status_code_is_200.yaml b/scanapi/tests/status_code_is_200.yaml
new file mode 100644
index 0000000..d1dd721
--- /dev/null
+++ b/scanapi/tests/status_code_is_200.yaml
@@ -0,0 +1,2 @@
+name: status_code_is_200
+assert: ${{ response.status_code == 200 }}
diff --git a/scanapi/tests/status_code_is_401.yaml b/scanapi/tests/status_code_is_401.yaml
new file mode 100644
index 0000000..af2b05b
--- /dev/null
+++ b/scanapi/tests/status_code_is_401.yaml
@@ -0,0 +1,2 @@
+name: status_code_is_401
+assert: ${{ response.status_code == 401 }}