Skip to content
Draft
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
38 changes: 35 additions & 3 deletions backend/data_management/table_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
from asyncpg import Connection


async def create_table(data_connection: Connection, table_name: str, schema: str):
async def create_table(data_connection: Connection, table_name: str, project_id: UUID):
"""Create a table in a project schema.

Args:
data_connection: Database connection
table_name: Name of the table to create
project_id: UUID of the project (used as schema name)
"""
schema = str(project_id)
existing_tables = await data_connection.fetch('''
SELECT tablename FROM pg_tables WHERE schemaname = $1 AND tablename = $2
''', schema, table_name)
Expand All @@ -27,7 +35,18 @@ async def delete_table(user_connection: Connection, data_connection: Connection,
await data_connection.execute(f'''DROP TABLE IF EXISTS "{project_id}"."{table_name}" CASCADE''')
await user_connection.execute(f'''DELETE FROM permissions."{project_id}" WHERE table_id = $1''', table_name)

async def set_cell_value(data_connection: Connection, schema: str, table_name: str, row: int, col: int, value: str):
async def set_cell_value(data_connection: Connection, project_id: UUID, table_name: str, row: int, col: int, value: str):
"""Set or update a cell value in a table.

Args:
data_connection: Database connection
project_id: UUID of the project (used as schema name)
table_name: Name of the table
row: Row index
col: Column index
value: Value to set (empty string deletes the cell)
"""
schema = str(project_id)
if not value:
await data_connection.execute(f'''DELETE FROM "{schema}"."{table_name}" WHERE row_index = $1 AND col_index = $2''', row, col)
else:
Expand All @@ -38,7 +57,20 @@ async def set_cell_value(data_connection: Connection, schema: str, table_name: s
DO UPDATE SET value = EXCLUDED.value
''', row, col, value)

async def get_cell_value(data_connection: Connection, schema: str, table_name: str, row: int, col: int) -> str | None:
async def get_cell_value(data_connection: Connection, project_id: UUID, table_name: str, row: int, col: int) -> str | None:
"""Get a cell value from a table.

Args:
data_connection: Database connection
project_id: UUID of the project (used as schema name)
table_name: Name of the table
row: Row index
col: Column index

Returns:
The cell value or None if not found
"""
schema = str(project_id)
result = await data_connection.fetchrow(f'''
SELECT value FROM "{schema}"."{table_name}" WHERE row_index = $1 AND col_index = $2
''', row, col)
Expand Down
3 changes: 3 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ markers =
test_db: Test related to the test database
data_db: Test related to the data database
user_creation: Test related to user creation
table_operations: Test related to table creation, deletion, and cell operations
table_permissions: Test related to table permission management
web: Test related to web routes and UI endpoints
132 changes: 132 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# Test Organization

This directory contains comprehensive tests for the FlowTables backend and web application.

## Test Files

### User Management Tests
- **test_user_handler.py** - Tests for user operations (create, get, delete, password validation, team membership)
- **test_team_handler.py** - Tests for team operations (create, get by ID/name, delete)
- **test_user_creation.py** - Basic user creation test

### Project Management Tests
- **test_projects.py** - Tests for project management (create, delete, members, roles)

### Table Management Tests
- **test_table_operations.py** - Tests for table lifecycle and cell operations
- Table creation and deletion
- Cell value operations (set, get, update, delete)
- Schema validation
- **test_table_permissions.py** - Tests for permission management
- Setting and updating permissions
- Getting user permissions
- Deleting permissions (all or by range)
- **test_table_handler.py** - Original comprehensive table tests (will be deprecated)

### Web/UI Tests
- **test_web_routes.py** - Tests for web application routes and pages
- Index/homepage
- Login/logout functionality
- Dashboard access control
- Error pages (403, 404, 500)
- Static file serving
- Session middleware
- Custom headers and middleware

### API Tests
- **test_api.py** - API endpoint tests

## Test Markers

Tests are organized with pytest markers for selective test execution:

- `@pytest.mark.user_creation` - User and team management tests
- `@pytest.mark.data_db` - Tests requiring data database
- `@pytest.mark.table_operations` - Table and cell operation tests
- `@pytest.mark.table_permissions` - Permission management tests
- `@pytest.mark.web` - Web route and UI tests

## Running Tests

### Run all tests
```bash
pytest
```

### Run specific marker groups
```bash
# User management tests
pytest -m user_creation

# Data database tests
pytest -m data_db

# Table operation tests only
pytest -m table_operations

# Permission tests only
pytest -m table_permissions

# Web/UI tests only
pytest -m web
```

### Run specific test files
```bash
pytest tests/test_user_handler.py
pytest tests/test_table_operations.py
```

## Test Fixtures

Common test fixtures are defined in `conftest.py`:

- `user_db_transaction` - User database connection with transaction rollback
- `data_db_transaction` - Data database connection with transaction rollback
- `test_user` - Creates a test user for use in tests
- `test_project` - Creates a test project with a test user
- `test_table` - Creates a test table in a test project

## Writing Tests

### Best Practices

1. **Use descriptive test names** - Test names should clearly indicate what is being tested
2. **Add assertion messages** - Include helpful messages for assertions to aid debugging
```python
assert user is not None, f"User {user_id} should be found"
```
3. **Use fixtures** - Leverage fixtures to reduce code duplication
4. **Add docstrings** - Document what each test does
5. **Test edge cases** - Include tests for error conditions and boundary cases

### Example Test Structure

```python
@pytest.mark.user_creation
@pytest.mark.asyncio
async def test_get_user_by_id(user_db_transaction):
"""Test retrieving user by ID."""
# Setup - Create test data
user_id = await create_user(...)

# Action - Perform the operation
user = await get_user_by_id(user_db_transaction, user_id)

# Assert - Verify expected results with clear messages
assert user is not None, f"User {user_id} should be found"
assert user['user_id'] == user_id, "User ID should match"
```

## Debugging Failed Tests

When a test fails, the assertion messages will help identify the problem:

```
AssertionError: User 123e4567-e89b-12d3-a456-426614174000 should be found
```

This tells you:
1. Which assertion failed
2. The expected behavior
3. The relevant data (user ID)
43 changes: 42 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,45 @@ async def user_db_transaction(user_db_pool):
try:
yield connection
finally:
await transaction.rollback()
await transaction.rollback()


# Helper fixtures for common test setup
@pytest_asyncio.fixture
async def test_user(user_db_transaction):
"""Create a test user for use in tests."""
from backend.user_management.user_handler import create_user
user_id = await create_user(
user_connection=user_db_transaction,
userName="test_user",
email="test@example.com",
password="TestPassword123!",
lastName="User",
firstName="Test"
)
return user_id


@pytest_asyncio.fixture
async def test_project(user_db_transaction, data_db_transaction, test_user):
"""Create a test project for use in tests."""
from backend.data_management.project_handler import create_project
project_id = await create_project(
user_connection=user_db_transaction,
data_connection=data_db_transaction,
project_name="Test Project",
owner_id=test_user
)
return project_id


@pytest_asyncio.fixture
async def test_table(data_db_transaction, test_project):
"""Create a test table in a test project."""
from backend.data_management.table_handler import create_table
from uuid import UUID
table_name = "test_table"
# Convert to UUID if test_project is a string
project_uuid = UUID(test_project) if isinstance(test_project, str) else test_project
await create_table(data_db_transaction, table_name, project_uuid)
return table_name, project_uuid
Loading