Skip to content
Merged
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
28 changes: 21 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,29 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
Use `uv` for all Python operations. Task runner commands via poethepoet:

```bash
uv run poe lint # Run ruff linting
uv run poe format # Run ruff formatting
uv run poe typecheck # Run ty type checking
uv run poe test # Run pytest
uv run poe check # Run lint + typecheck together
uv run poe lint # Run ruff linting
uv run poe format # Run ruff formatting
uv run poe typecheck # Run ty type checking
uv run poe test # Run all tests
uv run poe test-unit # Run unit tests only
uv run poe test-integration # Run integration tests only
uv run poe check # Run lint + typecheck together
```

Single test: `uv run pytest test/test_file.py::test_name -v`
Single test: `uv run pytest tests/unit/test_file.py::test_name -v`

Run the CLI: `uv run brix --help`

## Before Committing

**Always run pre-commit before committing to avoid CI failures:**

```bash
uv run pre-commit run --all-files
```

This runs ruff linting, ruff formatting, type checking, and tests. The CI will fail if formatting is not applied.

## Code Style

- Python 3.10+, strict type hints required (ANN rules enforced)
Expand All @@ -29,7 +41,9 @@ Run the CLI: `uv run brix --help`
## Project Structure

- `src/brix/` - Main package (src-layout)
- `test/` - Tests (relaxed rules: no type hints, docstrings, or assert warnings required)
- `tests/unit/` - Unit tests (mocked, isolated)
- `tests/integration/` - Integration tests (require dbt, marked with `@pytest.mark.integration`)
- `tests/fixtures/` - Test fixtures (e.g., minimal dbt project for integration tests)

## Architecture

Expand Down
14 changes: 12 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ build-backend = "hatchling.build"

[dependency-groups]
dev = [
"dbt-core>=1.9.0",
"dbt-duckdb>=1.9.0",
"poethepoet>=0.38.0",
"pre-commit>=4.5.0",
"pytest>=9.0.1",
Expand Down Expand Up @@ -73,7 +75,7 @@ unfixable = ["B"]

[tool.ruff.lint.per-file-ignores]
"__init__.py" = ["E402"]
"test/*" = ["S101", "ANN", "D"]
"tests/*" = ["S101", "ANN", "D"]

[tool.ruff.lint.pydocstyle]
convention = "google"
Expand All @@ -97,7 +99,7 @@ python-version = "3.10"
exclude = [".git", "dist"]

[[tool.ty.overrides]]
include = ["test/**"]
include = ["tests/**"]

[tool.ty.overrides.rules]
possibly-unresolved-reference = "ignore"
Expand All @@ -106,11 +108,19 @@ possibly-unresolved-reference = "ignore"
source = ["src"]
branch = true

[tool.pytest.ini_options]
testpaths = ["tests"]
markers = [
"integration: marks tests as integration tests (deselect with '-m \"not integration\"')",
]

[tool.poe.tasks]
lint = "ruff check ."
format = "ruff format ."
typecheck = "ty check"
test = "pytest"
test-unit = "pytest tests/unit"
test-integration = "pytest tests/integration -m integration"
check = ["lint", "typecheck"]

[tool.semantic_release]
Expand Down
Empty file added tests/__init__.py
Empty file.
24 changes: 24 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""Shared pytest fixtures for brix tests."""

import pytest

from brix import version_check
from brix.utils.logging import reset_logger


@pytest.fixture(autouse=True)
def reset_logger_fixture():
"""Reset logger before and after each test."""
reset_logger()
yield
reset_logger()


@pytest.fixture
def temp_cache_dir(tmp_path, monkeypatch):
"""Use temporary directory for version check cache."""
cache_dir = tmp_path / ".cache" / "brix"
cache_file = cache_dir / "version_check.json"
monkeypatch.setattr(version_check, "CACHE_DIR", cache_dir)
monkeypatch.setattr(version_check, "CACHE_FILE", cache_file)
return cache_file
9 changes: 9 additions & 0 deletions tests/fixtures/dbt_project/dbt_project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: 'test_project'
version: '1.0.0'

profile: 'test_profile'

model-paths: ["models"]
target-path: "target"
clean-targets:
- "target"
1 change: 1 addition & 0 deletions tests/fixtures/dbt_project/models/example_model.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
select 1 as id, 'test' as name
6 changes: 6 additions & 0 deletions tests/fixtures/dbt_project/profiles.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
test_profile:
target: dev
outputs:
dev:
type: duckdb
path: ':memory:'
Empty file added tests/integration/__init__.py
Empty file.
76 changes: 76 additions & 0 deletions tests/integration/test_dbt_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"""Integration tests for dbt passthrough with actual dbt execution."""

import shutil
from pathlib import Path

import pytest
from typer.testing import CliRunner

from brix.main import app
from brix.modules.dbt import run_dbt

# Path to the test dbt project fixture
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures"
DBT_PROJECT_DIR = FIXTURES_DIR / "dbt_project"

runner = CliRunner()


@pytest.fixture
def dbt_project(tmp_path):
"""Copy the dbt project fixture to a temporary directory."""
project_dir = tmp_path / "dbt_project"
shutil.copytree(DBT_PROJECT_DIR, project_dir)
return project_dir


@pytest.mark.integration
class TestDbtIntegration:
"""Integration tests that actually run dbt commands."""

def test_dbt_version(self):
"""Test that dbt --version runs successfully."""
exit_code = run_dbt(["--version"])
assert exit_code == 0

def test_dbt_debug(self, dbt_project):
"""Test dbt debug in a project directory."""
exit_code = run_dbt(["debug", "--project-dir", str(dbt_project), "--profiles-dir", str(dbt_project)])
assert exit_code == 0

def test_dbt_parse(self, dbt_project):
"""Test dbt parse to validate project structure."""
exit_code = run_dbt(["parse", "--project-dir", str(dbt_project), "--profiles-dir", str(dbt_project)])
assert exit_code == 0

def test_dbt_run(self, dbt_project):
"""Test dbt run executes models successfully."""
exit_code = run_dbt(["run", "--project-dir", str(dbt_project), "--profiles-dir", str(dbt_project)])
assert exit_code == 0


@pytest.mark.integration
class TestDbtCliIntegration:
"""Integration tests for dbt passthrough via the CLI."""

def test_brix_dbt_version(self):
"""Test brix dbt --version runs successfully."""
# Note: dbt output goes to stdout via subprocess, not through Typer's output
result = runner.invoke(app, ["dbt", "--version"])
assert result.exit_code == 0

def test_brix_dbt_debug(self, dbt_project):
"""Test brix dbt debug in a project directory."""
result = runner.invoke(
app,
["dbt", "debug", "--project-dir", str(dbt_project), "--profiles-dir", str(dbt_project)],
)
assert result.exit_code == 0

def test_brix_dbt_run(self, dbt_project):
"""Test brix dbt run executes models successfully via CLI."""
result = runner.invoke(
app,
["dbt", "run", "--project-dir", str(dbt_project), "--profiles-dir", str(dbt_project)],
)
assert result.exit_code == 0
Empty file added tests/unit/__init__.py
Empty file.
File renamed without changes.
10 changes: 0 additions & 10 deletions test/test_logging.py → tests/unit/test_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import logging
from unittest.mock import patch

import pytest
from typer.testing import CliRunner

from brix.main import app
Expand All @@ -14,19 +13,10 @@
LogConfig,
LogLevel,
get_logger,
reset_logger,
setup_logging,
)


@pytest.fixture(autouse=True)
def reset_logger_fixture():
"""Reset logger before and after each test."""
reset_logger()
yield
reset_logger()


class TestLogLevel:
def test_trace_below_debug(self):
assert LogLevel.TRACE < LogLevel.DEBUG
Expand Down
11 changes: 0 additions & 11 deletions test/test_version_check.py → tests/unit/test_version_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from unittest.mock import patch

import httpx
import pytest
import respx

from brix import version_check
Expand All @@ -19,16 +18,6 @@
)


@pytest.fixture
def temp_cache_dir(tmp_path, monkeypatch):
"""Use temporary directory for cache."""
cache_dir = tmp_path / ".cache" / "brix"
cache_file = cache_dir / "version_check.json"
monkeypatch.setattr(version_check, "CACHE_DIR", cache_dir)
monkeypatch.setattr(version_check, "CACHE_FILE", cache_file)
return cache_file


class TestVersionCache:
def test_valid_cache(self):
cache = VersionCache(last_check=datetime.now(timezone.utc), latest_version="1.0.0")
Expand Down
Loading