Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
kcmikee authored Feb 24, 2025
2 parents 08f7e3b + a6df6d8 commit e66c5e2
Show file tree
Hide file tree
Showing 26 changed files with 1,599 additions and 6,440 deletions.
50 changes: 50 additions & 0 deletions .github/workflows/margin_app_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Margin Tests

on:
push:
branches:
- main
paths:
- 'margin_app/**'
pull_request:
branches:
- main
paths:
- 'margin_app/**'
jobs:
shared:
uses: ./.github/workflows/shared_workflow.yml
with:
python-version: "3.12"

margin-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Create .env file
run: cp margin_app/.env.dev margin_app/.env

- name: Build and Start Containers
run: |
docker compose -f margin_app/docker-compose.yml --env-file margin_app/.env up -d --build
echo "Waiting for containers to be ready..."
sleep 30
- name: Run Integration Tests with Coverage
run: |
docker compose -f margin_app/docker-compose.yml exec backend python -m pytest app/tests -v
- name: Clean Up
if: always()
run: |
docker compose -f margin_app/docker-compose.yml logs > docker-logs.txt || true
docker compose -f margin_app/docker-compose.yml down -v
- name: Upload Docker Logs on Failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: docker-logs
path: docker-logs.txt
6,400 changes: 0 additions & 6,400 deletions frontend/yarn.lock

This file was deleted.

8 changes: 8 additions & 0 deletions margin_app/.env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
APP_ENV=development
SECRET_KEY=SECRET_KEY

# Database creds
POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=db_name
DB_HOST=db
2 changes: 0 additions & 2 deletions margin_app/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,3 @@ POSTGRES_USER=user
POSTGRES_PASSWORD=password
POSTGRES_DB=db_name
DB_HOST=localhost
DATABASE_URL=postgresql+asyncpg://user:***@localhost:5432/db_name

1 change: 0 additions & 1 deletion margin_app/app/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
database_url = os.getenv("DATABASE_URL")

config.set_main_option(
"sqlalchemy.url", settings.db_url.render_as_string(hide_password=False)
Expand Down
Empty file added margin_app/app/api/deposit.py
Empty file.
Empty file.
Empty file.
Empty file added margin_app/app/api/pools.py
Empty file.
Empty file added margin_app/app/api/user.py
Empty file.
Empty file.
Empty file.
Empty file.
Empty file added margin_app/app/schemas/pools.py
Empty file.
Empty file added margin_app/app/schemas/user.py
Empty file.
120 changes: 120 additions & 0 deletions margin_app/app/tests/deposit_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""
This module contains tests for the deposit CRUD functionality.
"""

import uuid
from decimal import Decimal
from unittest.mock import AsyncMock, MagicMock
import pytest
from sqlalchemy.exc import SQLAlchemyError

from app.crud.deposit import DepositCRUD
from app.models.deposit import Deposit


@pytest.fixture
def deposit_crud():
"""Fixture to create an instance of DepositCRUD."""
return DepositCRUD()


@pytest.fixture
def sample_deposit():
"""Create a sample deposit for testing."""
return Deposit(
id=uuid.uuid4(),
user_id=uuid.uuid4(),
token="NGN",
amount=Decimal("1.5"),
transaction_id="tx234"
)


### Positive Test Cases ###

@pytest.mark.asyncio
async def test_create_deposit_success(deposit_crud):
"""Test successfully creating a new deposit."""
user_id = uuid.uuid4()
token = "NGN"
amount = Decimal("1.5")
transaction_id = "tx234"

deposit_crud.write_to_db = AsyncMock(return_value=Deposit(
id=uuid.uuid4(), user_id=user_id, token=token, amount=amount, transaction_id=transaction_id
))

deposit = await deposit_crud.create_deposit(user_id, token, amount, transaction_id)

# Confirm tests
assert deposit.user_id == user_id
assert deposit.token == token
assert deposit.amount == amount
assert deposit.transaction_id == transaction_id
assert hasattr(deposit, "id")


@pytest.mark.asyncio
async def test_update_deposit_success(deposit_crud, sample_deposit):
"""Test successfully updating an existing deposit."""

mock_session_obj = MagicMock()
mock_session_obj.get = AsyncMock(return_value=sample_deposit)
mock_session_obj.commit = AsyncMock()
mock_session_obj.refresh = AsyncMock()

mock_session_obj.__aenter__.return_value = mock_session_obj
mock_session_obj.__aexit__.return_value = None

deposit_crud.session = MagicMock(return_value=mock_session_obj)

update_data = {"token": "SOL", "amount": Decimal("2.0"), "transaction_id": "tx365"}
updated_deposit = await deposit_crud.update_deposit(sample_deposit.id, update_data)

# Confirm tests
mock_session_obj.get.assert_called_once()
assert updated_deposit.token == "SOL"
assert updated_deposit.amount == Decimal("2.0")
assert updated_deposit.transaction_id == "tx365"


### Negative Test Cases ###

@pytest.mark.asyncio
async def test_create_deposit_failure(deposit_crud):
"""Test failure when creating a deposit."""
deposit_crud.write_to_db = AsyncMock(side_effect=SQLAlchemyError("DB error"))

with pytest.raises(Exception, match="Could not create deposit"):
await deposit_crud.create_deposit(uuid.uuid4(), "ETH", Decimal("3.5"), "tx222")


@pytest.mark.asyncio
async def test_update_deposit_not_found(deposit_crud):
"""Test updating a deposit that does not exist."""

mock_session_obj = MagicMock()
mock_session_obj.get = AsyncMock(return_value=None)
mock_session_obj.__aenter__.return_value = mock_session_obj
mock_session_obj.__aexit__.return_value = None

deposit_crud.session = MagicMock(return_value=mock_session_obj)

result = await deposit_crud.update_deposit(uuid.uuid4(), {"token": "BTC"})
assert result is None


@pytest.mark.asyncio
async def test_update_deposit_failure(deposit_crud, sample_deposit):
"""Test failure when updating a deposit due to commit error."""

mock_session_obj = MagicMock()
mock_session_obj.get = AsyncMock(return_value=sample_deposit)
mock_session_obj.commit = AsyncMock(side_effect=SQLAlchemyError("Commit error"))
mock_session_obj.__aenter__.return_value = mock_session_obj
mock_session_obj.__aexit__.return_value = None

deposit_crud.session = MagicMock(return_value=mock_session_obj)

with pytest.raises(SQLAlchemyError, match="Commit error"):
await deposit_crud.update_deposit(sample_deposit.id, {"token": "BTC"})
70 changes: 70 additions & 0 deletions margin_app/app/tests/liquidation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""Tests for LiquidationCRUD in spotnet/margin_app/app/crud/liquidation.py"""
# pylint: disable=redefined-outer-name

import pytest
from app.crud.liquidation import LiquidationCRUD
from app.models.liquidation import Liquidation

@pytest.fixture
def session_fixture():
"""Fixture providing a mock database session."""
class MockSession:
"""Mock session to simulate database operations."""
def __init__(self):
self.data = []

def add(self, obj):
"""Simulate adding an object to the database."""
self.data.append(obj)

def commit(self):
"""Simulate committing a transaction (no operation)."""

def query(self, _):
"""Return a mock query object."""
class Query:
"""Mock Query class to filter and retrieve data."""
def __init__(self, data):
self.data = data

def filter_by(self, **kwargs):
"""Filter data based on provided keyword arguments."""
filtered = [
item for item in self.data
if all(getattr(item, key, None) == value for key, value in kwargs.items())
]
return Query(filtered)

def first(self):
"""Return the first element in the filtered data, or None if empty."""
return self.data[0] if self.data else None

return Query(self.data)

return MockSession()

def test_create_liquidation_success(session_fixture):
"""Test creating a liquidation record with valid data."""
liquidation_data = {"id": 1, "amount": 1000, "status": "pending"}
liquidation = LiquidationCRUD().create(session_fixture, liquidation_data) # pylint: disable=no-member
assert liquidation is not None
assert liquidation.id == 1

def test_create_liquidation_failure(session_fixture):
"""Test that creating a liquidation record with invalid data raises an error."""
liquidation_data = {"amount": -500} # Invalid data: negative amount
with pytest.raises(ValueError):
LiquidationCRUD().create(session_fixture, liquidation_data) # pylint: disable=no-member

def test_get_liquidation_success(session_fixture):
"""Test retrieving an existing liquidation record."""
liquidation_instance = Liquidation(id=1, amount=1000, status="pending")
session_fixture.add(liquidation_instance)
liquidation = LiquidationCRUD().get(session_fixture, 1) # pylint: disable=no-member
assert liquidation is not None
assert liquidation.id == 1

def test_get_liquidation_not_found(session_fixture):
"""Test retrieving a non-existent liquidation record returns None."""
liquidation = LiquidationCRUD().get(session_fixture, 999) # pylint: disable=no-member
assert liquidation is None
12 changes: 12 additions & 0 deletions margin_app/app/tests/test_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
"""Test to check if worklow works properly"""

import pytest

@pytest.fixture
def sample_data():
"""Test fixture"""
return {"key": "value"}

def test_sample(sample_data):
"""Test function"""
assert sample_data["key"] == "value"
4 changes: 1 addition & 3 deletions margin_app/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

networks:
app_network:
driver: bridge
Expand All @@ -23,7 +21,7 @@ services:
networks:
- app_network
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U ${DB_USER}" ]
test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER}" ]
interval: 10s
timeout: 5s
retries: 5
Expand Down
Loading

0 comments on commit e66c5e2

Please sign in to comment.