Skip to content

Commit 8255d6d

Browse files
authored
add coverage guide and refacator flask and fastapi apps (#25)
* feat: add coverage in fastapi Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> feat: add coverage in flask-mongo Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> * feat: add coverage in flask-mongo Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> * feat: add coverage in fastapi-twilio Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> feat: add coverage in flask-mongo Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> feat: add coverage in fastapi-postgres Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com> * Update README.md --------- Signed-off-by: Animesh Pathak <kurosakiichigo.songoku@gmail.com>
1 parent c899ad7 commit 8255d6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+366
-3735
lines changed

fastapi-postgres/README.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Make the following requests to the respective endpoints -
1313

1414
```bash
1515
git clone https://github.com/keploy/samples-python.git && cd samples-python/fastapi-postgres
16+
pip3 install coverage
1617
pip3 install -r requirements.txt
1718
```
1819

@@ -21,8 +22,7 @@ pip3 install -r requirements.txt
2122
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
2223

2324
```bash
24-
curl -O https://raw.githubusercontent.com/keploy/keploy/main/keploy.sh && source keploy.sh
25-
keploy
25+
curl -O -L https:///keploy.io/install.sh && source install.sh
2626
```
2727

2828
### Starting the PostgreSQL Instance
@@ -34,9 +34,9 @@ docker-compose up -d
3434

3535
### Capture the Testcases
3636

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

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

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

100100
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.
101101

102+
![keploy testcase](./img/testcases.png)
103+
102104
## Run the Testcases
103105

104106
Now let's run the application in test mode.
105107

106108
```shell
107-
sudo -E PATH=$PATH keploy test -c "uvicorn application.main:app --reload" --delay 10
109+
sudo -E PATH=$PATH keploy test -c "python3 -m uvicorn application.main:app" --delay 10
108110
```
111+
We will get output something like below -
112+
113+
![keploy testcase](./img/testrun.png)
114+
115+
By making just 2 api call, we have generated a complete test suite for our application and acheived 72% coverage.
116+
117+
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 😄**
109118

110-
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 😄**

fastapi-postgres/application/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from sqlalchemy.orm import sessionmaker
44

55

6-
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@postgres:5432/studentdb"
6+
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/studentdb"
77

88
engine = create_engine(SQLALCHEMY_DATABASE_URL)
99
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
from fastapi import Depends, FastAPI, HTTPException
22
from sqlalchemy.orm import Session
3+
from sqlalchemy import create_engine
4+
from sqlalchemy.orm import sessionmaker
5+
from sqlalchemy.ext.declarative import declarative_base
6+
import asyncio
37

48
from . import models, crud, schemas
5-
from .database import SessionLocal, engine
69

10+
# Database setup
11+
SQLALCHEMY_DATABASE_URL = "postgresql://postgres:postgres@localhost:5432/studentdb"
12+
engine = create_engine(SQLALCHEMY_DATABASE_URL)
13+
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
14+
Base = declarative_base()
715
models.Base.metadata.create_all(bind=engine)
816

917
app = FastAPI()
1018

11-
1219
# Dependency
1320
def get_db():
1421
db = SessionLocal()
@@ -17,7 +24,6 @@ def get_db():
1724
finally:
1825
db.close()
1926

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

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

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

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

53-
5456
@app.delete('/students/{student_id}', response_model=schemas.Student)
5557
def delete_student(student_id: int, db: Session = Depends(get_db)):
5658
student = crud.delete_student(db, student_id=student_id)
5759
if student is None:
5860
raise HTTPException(status_code=404, detail=f'Student with ID={student_id} not found!!')
5961
return student
62+
63+
# Graceful shutdown
64+
@app.on_event("shutdown")
65+
async def shutdown_event():
66+
# Example: Close the database connection pool
67+
print("Shutting down...")
68+
# Assuming SQLAlchemy engine has a dispose method to release resources
69+
engine.dispose()
70+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import pytest
2+
from sqlalchemy import create_engine
3+
from sqlalchemy.orm import sessionmaker
4+
5+
from .database import Base, SessionLocal
6+
from . import crud, models, schemas
7+
8+
# Create an in-memory SQLite database for testing
9+
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
10+
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
11+
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
12+
13+
# Create the database tables
14+
Base.metadata.create_all(bind=engine)
15+
16+
@pytest.fixture(scope="module")
17+
def db_session():
18+
connection = engine.connect()
19+
transaction = connection.begin()
20+
session = TestingSessionLocal(bind=connection)
21+
22+
yield session
23+
24+
session.close()
25+
transaction.rollback()
26+
connection.close()
27+
28+
def test_create_student(db_session):
29+
student_in = schemas.StudentCreate(name="John Doe", email="john.doe@example.com", password="password", stream="Science")
30+
student = crud.create_student(db_session, student_in)
31+
assert student.name == "John Doe"
32+
assert student.email == "john.doe@example.com"
33+
34+
def test_get_student(db_session):
35+
student_in = schemas.StudentCreate(name="Jane Doe", email="jane.doe@example.com", password="password", stream="Arts")
36+
student = crud.create_student(db_session, student_in)
37+
fetched_student = crud.get_student(db_session, student.id)
38+
assert fetched_student.name == "Jane Doe"
39+
assert fetched_student.email == "jane.doe@example.com"
40+
41+
def test_update_student(db_session):
42+
student_in = schemas.StudentCreate(name="Jim Beam", email="jim.beam@example.com", password="password", stream="Commerce")
43+
student = crud.create_student(db_session, student_in)
44+
student_update = schemas.StudentUpdate(name="Jim Updated", email="jim.updated@example.com", password="newpassword", stream="Commerce")
45+
updated_student = crud.update_student(db_session, student_update, student.id)
46+
assert updated_student.name == "Jim Updated"
47+
assert updated_student.email == "jim.updated@example.com"
48+
49+
def test_delete_student(db_session):
50+
student_in = schemas.StudentCreate(name="Will Smith", email="will.smith@example.com", password="password", stream="Engineering")
51+
student = crud.create_student(db_session, student_in)
52+
deleted_student = crud.delete_student(db_session, student.id)
53+
assert deleted_student is not None
54+
assert deleted_student.name == "Will Smith"
55+
assert crud.get_student(db_session, student.id) is None

fastapi-postgres/docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
version: '3'
1+
version: '3.9'
22
services:
33

44
postgres:
@@ -30,4 +30,4 @@ services:
3030

3131
networks:
3232
keploy-network:
33-
external: true
33+
external: true

fastapi-postgres/img/testcases.png

268 KB
Loading

fastapi-postgres/img/testrun.png

130 KB
Loading

fastapi-postgres/keploy.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
path: ""
2+
appId: 0
3+
appName: ""
4+
command: uvicorn application.main:app --reload
5+
port: 0
6+
dnsPort: 26789
7+
proxyPort: 16789
8+
debug: false
9+
disableTele: false
10+
disableANSI: false
11+
containerName: ""
12+
networkName: ""
13+
buildDelay: 30
14+
test:
15+
selectedTests: {}
16+
globalNoise:
17+
global: {}
18+
test-sets: {}
19+
delay: 5
20+
apiTimeout: 5
21+
skipCoverage: false
22+
coverageReportPath: ""
23+
ignoreOrdering: true
24+
mongoPassword: default@123
25+
language: ""
26+
removeUnusedMocks: false
27+
fallBackOnMiss: false
28+
jacocoAgentPath: ""
29+
basePath: ""
30+
mocking: true
31+
ignoredTests: {}
32+
record:
33+
filters: []
34+
recordTimer: 0s
35+
configPath: ""
36+
bypassRules: []
37+
generateGithubActions: true
38+
keployContainer: keploy-v2
39+
keployNetwork: keploy-network
40+
cmdType: native
41+
inCi: false
42+
43+
# Visit [https://keploy.io/docs/running-keploy/configuration-file/] to learn about using keploy through configration file.

0 commit comments

Comments
 (0)