Skip to content

Feat/ci pipelines#3

Merged
spuerta10 merged 25 commits into
mainfrom
feat/ci-pipelines
Oct 23, 2025
Merged

Feat/ci pipelines#3
spuerta10 merged 25 commits into
mainfrom
feat/ci-pipelines

Conversation

@spuerta10
Copy link
Copy Markdown
Owner

@spuerta10 spuerta10 commented Oct 22, 2025

Summary by Sourcery

Configure CI workflows to include unit and integration testing with coverage reporting and add a suite of end-to-end tests for ticket registration, including test fixtures and environment setup.

Enhancements:

  • Expose database environment variables in Docker Compose for integration test runs
  • Add psycopg2-binary, requests, and charset-normalizer dependencies and include types-requests for type checking

CI:

  • Add run-integration-tests job in GitHub Actions that installs dependencies, brings up services via Docker Compose, and executes pytest integration tests
  • Update static-code-analysis workflow to run unit tests from tests/unit and upload coverage artifacts
  • Comment out SonarQube scan placeholders in PR validations workflow

Tests:

  • Add integration tests for ticket registration flow covering success, duplicate registration, and invalid payload scenarios
  • Introduce pytest fixtures for database connection and base URL
  • Update pytest.ini to include both tests/unit and tests/integration directories

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Oct 22, 2025

Reviewer's Guide

Introduces a new CI integration test job, refines static code analysis workflow to handle unit and coverage artifacts, extends Docker Compose and pytest configuration for integration testing, updates dependencies, and adds a full suite of integration tests with fixtures.

Class diagram for integration test fixtures and structure

classDiagram
  class conftest_py {
    <<fixture>>
  }
  class test_tickets_registration_py {
    <<integration test>>
  }
  test_tickets_registration_py ..> conftest_py : uses fixture
Loading

Flow diagram for updated pytest test discovery

flowchart TD
  A["pytest.ini"] --> B["testpaths"]
  B --> C["tests/unit"]
  B --> D["tests/integration"]
Loading

File-Level Changes

Change Details Files
Add run-integration-tests CI job
  • Inserted new job after static-code-analysis in pr-validations workflow
  • Configured uv environment and dependencies installation
  • Loaded environment variables from .env.test
  • Started services with docker-compose and waited for PostgreSQL
  • Executed integration tests via pytest
.github/workflows/pr-validations.yml
Refine static-code-analysis workflow
  • Scoped unit tests to tests/unit directory
  • Uploaded coverage reports as artifacts
  • Retained console-only coverage path
  • Adjusted pytest invocation flags based on coverage_artifact input
.github/workflows/static-code-analysis.yml
Expose database environment variables in Docker Compose
  • Added DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME to registration API service
docker/docker-compose.yml
Restrict pytest to unit and integration tests
  • Updated pytest.ini testpaths to include tests/unit and tests/integration
pytest.ini
Update project dependencies
  • Added charset-normalizer, psycopg2-binary, and requests to requirements.txt
requirements.txt
Enhance pre-commit typing support
  • Included types-requests under additional_dependencies for mypy
.pre-commit-config.yaml
Introduce integration tests and fixtures
  • Created tests for ticket registration success and failure scenarios
  • Added pytest fixtures for db_connection and base_url
  • Added .env.test for test environment variables
tests/integration/test_tickets_registration.py
tests/integration/conftest.py
.env.test

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

Blocking issues:

  • An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. (link)

General comments:

  • Remove or enable the commented-out SonarQube job in pr-validations.yml to avoid confusion or stale code.
  • Replace the fixed sleep 10 in the integration test job with a proper health-check or wait-for script to ensure the database is ready before running tests.
  • Add a teardown step after integration tests to bring down the Docker Compose services and avoid resource leaks on the runner.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Remove or enable the commented-out SonarQube job in pr-validations.yml to avoid confusion or stale code.
- Replace the fixed `sleep 10` in the integration test job with a proper health-check or wait-for script to ensure the database is ready before running tests.
- Add a teardown step after integration tests to bring down the Docker Compose services and avoid resource leaks on the runner.

## Individual Comments

### Comment 1
<location> `docker/docker-compose.yml:22-27` </location>
<code_context>
     build:
       context: .. # root folder
       dockerfile: docker/Dockerfile.register-ticket-api
+    environment:
+      DB_HOST: ${DB_HOST}
+      DB_PORT: ${DB_PORT}
+      DB_USER: ${DB_USER}
+      DB_PASSWORD: ${DB_PASSWORD}
+      DB_NAME: ${DB_NAME}
     ports:
       - "8000:8000"
</code_context>

<issue_to_address>
**🚨 suggestion (security):** Passing environment variables directly from host may expose sensitive data.

Use secure methods like Docker secrets or a vault to manage sensitive environment variables, and avoid exposing them in logs or version control.

Suggested implementation:

```
    environment:
      DB_HOST: ${DB_HOST}
      DB_PORT: ${DB_PORT}
      DB_NAME: ${DB_NAME}
    secrets:
      - db_user
      - db_password

```

```
    ports:
      - "8000:8000"
    depends_on:

```

```
secrets:
  db_user:
    file: ./secrets/db_user.txt
  db_password:
    file: ./secrets/db_password.txt

```

1. You must create the files `docker/secrets/db_user.txt` and `docker/secrets/db_password.txt` containing the respective secret values.
2. In your application, read the DB_USER and DB_PASSWORD from `/run/secrets/db_user` and `/run/secrets/db_password` instead of environment variables.
3. Optionally, you can also move DB_HOST, DB_PORT, and DB_NAME to secrets if they are sensitive.
4. Make sure `.gitignore` includes the `secrets/` directory to avoid committing secrets to version control.
</issue_to_address>

### Comment 2
<location> `tests/integration/test_tickets_registration.py:80` </location>
<code_context>
+    assert second_response.status_code == ERROR_STATUS_CODE
+
+
+def test_invalid_ticket_registration_error(base_url: str, test_username: str = "spuertaf") -> None:
+    headers: dict[str, str] = {"Content-Type": "application/json"}
+    payload: dict[str, str] = {"seat": "B05", "gate": "G12"}
</code_context>

<issue_to_address>
**suggestion (testing):** Clarify what makes the ticket registration invalid in this test.

Add a comment or assertion explaining why 'seat': 'B05' and 'gate': 'G12' are invalid, or include more explicit invalid cases to clarify the business rule being tested.
</issue_to_address>

### Comment 3
<location> `tests/integration/conftest.py:9-19` </location>
<code_context>
+from psycopg2._psycopg import connection
+
+
+@pytest.fixture(scope="session")
+def db_connection() -> Generator:
+    conn: connection = psycopg2.connect(
+        dbname=os.getenv("DB_NAME", "event_access"),
</code_context>

<issue_to_address>
**suggestion (testing):** Consider using a transaction rollback for database fixture to isolate tests.

Using a per-test transaction with rollback will prevent data leakage between tests and improve reliability.

```suggestion
@pytest.fixture(scope="function")
def db_connection() -> Generator:
    conn: connection = psycopg2.connect(
        dbname=os.getenv("DB_NAME", "event_access"),
        user=os.getenv("DB_USER", "test"),
        password=os.getenv("DB_PASSWORD", "TEST"),
        host=os.getenv("DB_HOST", "localhost"),
        port=os.getenv("DB_PORT", "5432"),
    )
    # Start a transaction
    conn.autocommit = False
    try:
        yield conn
    finally:
        # Rollback any changes made during the test
        conn.rollback()
        conn.close()
```
</issue_to_address>

### Comment 4
<location> `.github/workflows/pr-validations.yml:42` </location>
<code_context>
        uses: astral-sh/setup-uv@v5
</code_context>

<issue_to_address>
**security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha):** An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

*Source: opengrep*
</issue_to_address>

### Comment 5
<location> `tests/integration/test_tickets_registration.py:54` </location>
<code_context>
def test_register_ticket_flow(
    base_url: str, db_connection: connection, test_username: str = "spuertaf"
) -> None:
    """
    Tests whole ticket registration flow works as expected.
    """
    headers: dict[str, str] = {"Content-Type": "application/json"}
    payload: dict[str, str] = {"seat": "A12", "gate": "G1"}
    endpoint: str = f"/api/users/{test_username}/tickets"

    response = requests.post(
        url=base_url + endpoint, headers=headers, json=payload, timeout=REQUEST_TIMEOUT
    )

    assert response.status_code == OK_STATUS_CODE

    ticket_id: str = response.json().get("id")
    assert ticket_id is not None

    response_user_id: str = response.json().get("user_id")
    assert response_user_id is not None

    used_at = response.json().get("used_at")
    assert used_at is None

    # see if database registered the ticket
    with db_connection.cursor() as cursor:
        cursor.execute(
            """
            SELECT
                seat, gate, user_id, status
            FROM tickets
            WHERE
                ticket_id = %s
            """,
            (ticket_id,),
        )
        result: tuple[Any, Any, Any, Any] = cursor.fetchone()

    assert result is not None
    seat, gate, user_id, status = result
    assert seat == payload["seat"]
    assert gate == payload["gate"]
    assert str(user_id) == str(response_user_id)
    assert status == "valid"

</code_context>

<issue_to_address>
**suggestion (code-quality):** Remove unnecessary casts to int, str, float or bool ([`remove-unnecessary-cast`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/remove-unnecessary-cast/))

```suggestion
    assert str(user_id) == response_user_id
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread docker/docker-compose.yml
Comment on lines +22 to +27
environment:
DB_HOST: ${DB_HOST}
DB_PORT: ${DB_PORT}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 suggestion (security): Passing environment variables directly from host may expose sensitive data.

Use secure methods like Docker secrets or a vault to manage sensitive environment variables, and avoid exposing them in logs or version control.

Suggested implementation:

    environment:
      DB_HOST: ${DB_HOST}
      DB_PORT: ${DB_PORT}
      DB_NAME: ${DB_NAME}
    secrets:
      - db_user
      - db_password

    ports:
      - "8000:8000"
    depends_on:

secrets:
  db_user:
    file: ./secrets/db_user.txt
  db_password:
    file: ./secrets/db_password.txt

  1. You must create the files docker/secrets/db_user.txt and docker/secrets/db_password.txt containing the respective secret values.
  2. In your application, read the DB_USER and DB_PASSWORD from /run/secrets/db_user and /run/secrets/db_password instead of environment variables.
  3. Optionally, you can also move DB_HOST, DB_PORT, and DB_NAME to secrets if they are sensitive.
  4. Make sure .gitignore includes the secrets/ directory to avoid committing secrets to version control.

Comment on lines +9 to +19
@pytest.fixture(scope="session")
def db_connection() -> Generator:
conn: connection = psycopg2.connect(
dbname=os.getenv("DB_NAME", "event_access"),
user=os.getenv("DB_USER", "test"),
password=os.getenv("DB_PASSWORD", "TEST"),
host=os.getenv("DB_HOST", "localhost"),
port=os.getenv("DB_PORT", "5432"),
)
yield conn
conn.close()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider using a transaction rollback for database fixture to isolate tests.

Using a per-test transaction with rollback will prevent data leakage between tests and improve reliability.

Suggested change
@pytest.fixture(scope="session")
def db_connection() -> Generator:
conn: connection = psycopg2.connect(
dbname=os.getenv("DB_NAME", "event_access"),
user=os.getenv("DB_USER", "test"),
password=os.getenv("DB_PASSWORD", "TEST"),
host=os.getenv("DB_HOST", "localhost"),
port=os.getenv("DB_PORT", "5432"),
)
yield conn
conn.close()
@pytest.fixture(scope="function")
def db_connection() -> Generator:
conn: connection = psycopg2.connect(
dbname=os.getenv("DB_NAME", "event_access"),
user=os.getenv("DB_USER", "test"),
password=os.getenv("DB_PASSWORD", "TEST"),
host=os.getenv("DB_HOST", "localhost"),
port=os.getenv("DB_PORT", "5432"),
)
# Start a transaction
conn.autocommit = False
try:
yield conn
finally:
# Rollback any changes made during the test
conn.rollback()
conn.close()

Comment thread .github/workflows/pr-validations.yml Outdated
seat, gate, user_id, status = result
assert seat == payload["seat"]
assert gate == payload["gate"]
assert str(user_id) == str(response_user_id)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Remove unnecessary casts to int, str, float or bool (remove-unnecessary-cast)

Suggested change
assert str(user_id) == str(response_user_id)
assert str(user_id) == response_user_id

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

Comment thread .github/workflows/pr-validations.yml Outdated
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

- uses: actions/checkout@v4

- name: Log in Github container registry
uses: docker/login-action@v3
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha): An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

Source: opengrep

password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker image to GHCR
uses: docker/build-push-action@v5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha): An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

Source: opengrep

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

- uses: actions/checkout@v4

- name: Install uv
uses: astral-sh/setup-uv@v5
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha): An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

Source: opengrep

uv pip install -r requirements.txt

- name: Load env vars from .env.test file
uses: xom9ikk/dotenv@v2
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (yaml.github-actions.security.third-party-action-not-pinned-to-commit-sha): An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

Source: opengrep

@spuerta10 spuerta10 merged commit 86fe8f7 into main Oct 23, 2025
4 of 5 checks passed
@spuerta10 spuerta10 deleted the feat/ci-pipelines branch October 23, 2025 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant