Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions fastapi-postgres/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Make the following requests to the respective endpoints -

```bash
git clone https://github.com/keploy/samples-python.git && cd samples-python/fastapi-postgres
pip3 install coverage
pip3 install -r requirements.txt
```

Expand All @@ -21,8 +22,7 @@ pip3 install -r requirements.txt
Keploy can be installed on Linux directly and on Windows with the help of WSL. Based on your system architecture, install the keploy latest binary release

```bash
curl -O https://raw.githubusercontent.com/keploy/keploy/main/keploy.sh && source keploy.sh
keploy
curl -O -L https:///keploy.io/install.sh && source install.sh
```

### Starting the PostgreSQL Instance
Expand All @@ -34,9 +34,9 @@ docker-compose up -d

### Capture the Testcases

This command will start the recording of API calls using ebpf:-
This command will start the recording of API calls :

```shell
```sh
sudo -E PATH=$PATH keploy record -c "uvicorn application.main:app --reload"
```

Expand Down Expand Up @@ -99,12 +99,20 @@ curl --location --request DELETE 'http://127.0.0.1:8000/students/1'

Now all these API calls were captured as **editable** testcases and written to `keploy/tests` folder. The keploy directory would also have `mocks` file that contains all the outputs of postgres operations.

![keploy testcase](./img/testcases.png)

## Run the Testcases

Now let's run the application in test mode.

```shell
sudo -E PATH=$PATH keploy test -c "uvicorn application.main:app --reload" --delay 10
sudo -E PATH=$PATH keploy test -c "python3 -m uvicorn application.main:app" --delay 10
```
We will get output something like below -

![keploy testcase](./img/testrun.png)

By making just 2 api call, we have generated a complete test suite for our application and acheived 72% coverage.

So, no need to setup fake database/apis like Postgres or write mocks for them. Keploy automatically mocks them and, **The application thinks it's talking to Postgres 😄**

So, no need to setup fake database/apis like Postgres or write mocks for them. Keploy automatically mocks them and, **The application thinks it's talking to Postgres 😄**
2 changes: 1 addition & 1 deletion fastapi-postgres/application/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from sqlalchemy.orm import sessionmaker


SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@postgres:5432/studentdb"
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/studentdb"

engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Expand Down
25 changes: 18 additions & 7 deletions fastapi-postgres/application/main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import asyncio

from . import models, crud, schemas
from .database import SessionLocal, engine

# Database setup
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/studentdb"
engine = create_engine(SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
models.Base.metadata.create_all(bind=engine)

app = FastAPI()


# Dependency
def get_db():
db = SessionLocal()
Expand All @@ -17,7 +24,6 @@ def get_db():
finally:
db.close()


@app.post('/students/', response_model=schemas.Student)
def create_student(student: schemas.StudentCreate, db: Session = Depends(get_db)):
db_student = crud.get_student_byEmail(db, student_email=student.email)
Expand All @@ -26,34 +32,39 @@ def create_student(student: schemas.StudentCreate, db: Session = Depends(get_db)
data = crud.create_student(db, student=student)
return data


@app.get('/students/', response_model=list[schemas.Student])
def read_students(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
students = crud.get_students(db, skip, limit)
if students == []:
raise HTTPException(404, 'Data not found!!')
return students


@app.get('/students/{student_id}', response_model=schemas.Student)
def read_student(student_id: int, db: Session = Depends(get_db)):
student = crud.get_student(db, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student


@app.put('/students/{student_id}', response_model=schemas.Student)
def update_student(student_id: int, student: schemas.StudentUpdate, db: Session = Depends(get_db)):
student = crud.update_student(db, student=student, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student


@app.delete('/students/{student_id}', response_model=schemas.Student)
def delete_student(student_id: int, db: Session = Depends(get_db)):
student = crud.delete_student(db, student_id=student_id)
if student is None:
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
return student

# Graceful shutdown
@app.on_event("shutdown")
async def shutdown_event():
# Example: Close the database connection pool
print("Shutting down...")
# Assuming SQLAlchemy engine has a dispose method to release resources
engine.dispose()

55 changes: 55 additions & 0 deletions fastapi-postgres/application/test_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from .database import Base, SessionLocal
from . import crud, models, schemas

# Create an in-memory SQLite database for testing
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# Create the database tables
Base.metadata.create_all(bind=engine)

@pytest.fixture(scope="module")
def db_session():
connection = engine.connect()
transaction = connection.begin()
session = TestingSessionLocal(bind=connection)

yield session

session.close()
transaction.rollback()
connection.close()

def test_create_student(db_session):
student_in = schemas.StudentCreate(name="John Doe", email="john.doe@example.com", password="password", stream="Science")
student = crud.create_student(db_session, student_in)
assert student.name == "John Doe"
assert student.email == "john.doe@example.com"

def test_get_student(db_session):
student_in = schemas.StudentCreate(name="Jane Doe", email="jane.doe@example.com", password="password", stream="Arts")
student = crud.create_student(db_session, student_in)
fetched_student = crud.get_student(db_session, student.id)
assert fetched_student.name == "Jane Doe"
assert fetched_student.email == "jane.doe@example.com"

def test_update_student(db_session):
student_in = schemas.StudentCreate(name="Jim Beam", email="jim.beam@example.com", password="password", stream="Commerce")
student = crud.create_student(db_session, student_in)
student_update = schemas.StudentUpdate(name="Jim Updated", email="jim.updated@example.com", password="newpassword", stream="Commerce")
updated_student = crud.update_student(db_session, student_update, student.id)
assert updated_student.name == "Jim Updated"
assert updated_student.email == "jim.updated@example.com"

def test_delete_student(db_session):
student_in = schemas.StudentCreate(name="Will Smith", email="will.smith@example.com", password="password", stream="Engineering")
student = crud.create_student(db_session, student_in)
deleted_student = crud.delete_student(db_session, student.id)
assert deleted_student is not None
assert deleted_student.name == "Will Smith"
assert crud.get_student(db_session, student.id) is None
4 changes: 2 additions & 2 deletions fastapi-postgres/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3'
version: '3.9'
services:

postgres:
Expand Down Expand Up @@ -30,4 +30,4 @@ services:

networks:
keploy-network:
external: true
external: true
Binary file added fastapi-postgres/img/testcases.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added fastapi-postgres/img/testrun.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions fastapi-postgres/keploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
path: ""
appId: 0
appName: ""
command: uvicorn application.main:app --reload
port: 0
dnsPort: 26789
proxyPort: 16789
debug: false
disableTele: false
disableANSI: false
containerName: ""
networkName: ""
buildDelay: 30
test:
selectedTests: {}
globalNoise:
global: {}
test-sets: {}
delay: 5
apiTimeout: 5
skipCoverage: false
coverageReportPath: ""
ignoreOrdering: true
mongoPassword: default@123
language: ""
removeUnusedMocks: false
fallBackOnMiss: false
jacocoAgentPath: ""
basePath: ""
mocking: true
ignoredTests: {}
record:
filters: []
recordTimer: 0s
configPath: ""
bypassRules: []
generateGithubActions: true
keployContainer: keploy-v2
keployNetwork: keploy-network
cmdType: native
inCi: false

# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file.
Loading