Skip to content

Commit abb862f

Browse files
committed
Add testing with vcr.py and a compose file
1 parent 408148e commit abb862f

File tree

10 files changed

+2163
-19
lines changed

10 files changed

+2163
-19
lines changed

.github/workflows/ruff.yaml

+1-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ jobs:
1919
run: uv venv
2020
- name: Install dependencies
2121
run: |
22-
uv pip install .
23-
uv pip install .[dev]
22+
uv sync
2423
- name: Run Ruff linter
2524
run: uv run ruff check .
2625
- name: Run Ruff formatter

.github/workflows/test.yaml

+27-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,30 @@
1-
name: CI
2-
on: [workflow_dispatch, pull_request, push]
1+
name: Tests
2+
3+
on:
4+
pull_request:
5+
branches: [ main ]
36

47
jobs:
5-
test:
6-
if: false
8+
pytest:
79
runs-on: ubuntu-latest
8-
steps: [uses: fastai/workflows/nbdev-ci@master]
10+
steps:
11+
- uses: actions/checkout@v3
12+
- name: Set up Python
13+
uses: actions/setup-python@v4
14+
with:
15+
python-version: '3.10'
16+
- name: Install uv
17+
run: pip install uv
18+
- name: Create venv
19+
run: uv venv
20+
- name: Install dependencies
21+
run: |
22+
uv sync
23+
- name: Start docker-compose
24+
run: docker compose up -d
25+
- name: Run Test
26+
run: uv run pytest
27+
- name: Logs
28+
run: docker compose logs
29+
- name: Stop docker-compose
30+
run: docker compose down

docker-compose.yaml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
services:
2+
db:
3+
image: timescale/timescaledb-ha:pg16
4+
ports:
5+
- "5432:5432"
6+
environment:
7+
- POSTGRES_PASSWORD=postgres
8+
- POSTGRES_USER=postgres
9+
- POSTGRES_DB=postgres
10+
- TIMESCALEDB_TELEMETRY=off
11+
volumes:
12+
- ./data:/var/lib/postgresql/data

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,5 @@ dev-dependencies = [
116116
"pandas>=2.2.3",
117117
"pytest-asyncio>=0.24.0",
118118
"pyright>=1.1.386",
119+
"vcrpy>=6.0.2",
119120
]

tests/conftest.py

+13-4
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,28 @@
22

33
import psycopg2
44
import pytest
5-
from dotenv import find_dotenv, load_dotenv
5+
6+
# from dotenv import find_dotenv, load_dotenv
7+
8+
9+
@pytest.fixture(scope="module")
10+
def setup_env_variables() -> None:
11+
os.environ.clear()
12+
os.environ["TIMESCALE_SERVICE_URL"] = "postgres://postgres:postgres@localhost:5432/postgres"
13+
os.environ["OPENAI_API_KEY"] = "fake key"
614

715

816
@pytest.fixture(scope="module")
9-
def service_url() -> str:
10-
_ = load_dotenv(find_dotenv(), override=True)
17+
def service_url(setup_env_variables: None) -> str: # noqa: ARG001
18+
# _ = load_dotenv(find_dotenv(), override=True)
1119
return os.environ["TIMESCALE_SERVICE_URL"]
1220

1321

1422
@pytest.fixture(scope="module", autouse=True)
15-
def create_temp_schema(service_url: str) -> None:
23+
def setup_db(service_url: str) -> None:
1624
conn = psycopg2.connect(service_url)
1725
with conn.cursor() as cursor:
26+
cursor.execute("CREATE EXTENSION IF NOT EXISTS ai CASCADE;")
1827
cursor.execute("CREATE SCHEMA IF NOT EXISTS temp;")
1928
conn.commit()
2029
conn.close()

tests/pg_vectorizer_test.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
from typing import Any
33

44
import psycopg2
5-
import pytest
65
from langchain.docstore.document import Document
76
from langchain.text_splitter import CharacterTextSplitter
87
from langchain_community.vectorstores.timescalevector import TimescaleVector
98
from langchain_openai import OpenAIEmbeddings
109

10+
from tests.utils import http_recorder
1111
from timescale_vector import client
1212
from timescale_vector.pgvectorizer import Vectorize
1313

@@ -31,7 +31,7 @@ def get_document(blog: dict[str, Any]) -> list[Document]:
3131
return docs
3232

3333

34-
@pytest.mark.skip(reason="requires OpenAI API key")
34+
@http_recorder.use_cassette("pg_vectorizer.yaml")
3535
def test_pg_vectorizer(service_url: str) -> None:
3636
with psycopg2.connect(service_url) as conn, conn.cursor() as cursor:
3737
for item in ["blog", "blog_embedding_work_queue", "blog_embedding"]:

tests/utils.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import os
2+
from typing import Any
3+
4+
import vcr
5+
6+
vcr_cassette_path = os.path.join(os.path.dirname(__file__), "vcr_cassettes")
7+
8+
9+
def remove_set_cookie_header(response: dict[str, Any]):
10+
"""
11+
Removes the Set-Cookie header from a VCR.py response object to improve cassette consistency.
12+
13+
This function can be used as a before_record callback in your VCR configuration
14+
to ensure that Set-Cookie headers are stripped from responses before they are
15+
recorded to cassettes.
16+
17+
Args:
18+
response (vcr.request.Response): The VCR.py response object to modify
19+
20+
Returns:
21+
vcr.request.Response: The modified response object with Set-Cookie headers removed
22+
23+
Example:
24+
import vcr
25+
26+
# Configure VCR with the callback
27+
vcr = vcr.VCR(
28+
before_record_response=remove_set_cookie_header,
29+
match_on=['uri', 'method']
30+
)
31+
32+
with vcr.use_cassette('tests/fixtures/my_cassette.yaml'):
33+
# Make your HTTP requests here
34+
pass
35+
"""
36+
37+
# Get the headers from the response
38+
headers = response["headers"]
39+
40+
# Headers to remove (case-insensitive)
41+
headers_to_remove = ["set-cookie", "Set-Cookie"]
42+
43+
# Remove Set-Cookie headers if they exist
44+
for header in headers_to_remove:
45+
if header in headers:
46+
del headers[header]
47+
48+
return response
49+
50+
51+
http_recorder = vcr.VCR(
52+
cassette_library_dir=vcr_cassette_path,
53+
record_mode="once",
54+
filter_headers=["authorization", "cookie"],
55+
before_record_response=remove_set_cookie_header,
56+
)

0 commit comments

Comments
 (0)