-
Notifications
You must be signed in to change notification settings - Fork 1
fix(ci): restore the CI Gate required check on the default branch #155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| from dotenv import load_dotenv | ||
| from flask_sqlalchemy import SQLAlchemy | ||
|
|
@@ -15,15 +16,17 @@ | |
| ) | ||
|
|
||
| # Load environment variables from .env file | ||
| load_dotenv() | ||
|
Check warning on line 19 in src/ledgerbase/__init__.py
|
||
|
|
||
| _DATABASE_URI_UNSET_MSG = "DATABASE_URL environment variable is not set." | ||
|
|
||
| # Initialize SQLAlchemy instance (app-bound later) | ||
| db = SQLAlchemy() | ||
|
|
||
| # Conditionally initialize Sentry for error monitoring | ||
| sentry_dsn = os.getenv("SENTRY_DSN") | ||
| if sentry_dsn: | ||
| sentry_init( | ||
|
Check warning on line 29 in src/ledgerbase/__init__.py
|
||
| dsn=sentry_dsn, | ||
| integrations=[FlaskIntegration()], | ||
| traces_sample_rate=float(os.getenv("SENTRY_TRACES_SAMPLE_RATE", "1.0")), | ||
|
|
@@ -35,23 +38,23 @@ | |
|
|
||
| def create_app() -> Flask: | ||
| """Application factory function.""" | ||
| project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) | ||
| template_dir = os.path.join(project_root, "templates") | ||
| project_root = Path(__file__).resolve().parent.parent | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -euo pipefail
fd -t d '^templates$' . -E .venv -E .git
rg -n -C 3 'project_root|template_dir|template_folder' src/ledgerbase/__init__.py tests/conftest.pyRepository: williaby/ledgerbase Length of output: 1472 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n src/ledgerbase/__init__.py | sed -n '1,90p'
printf '\n--- tests/conftest.py ---\n'
cat -n tests/conftest.py | sed -n '1,45p'
printf '\n--- repository template files ---\n'
find templates -maxdepth 2 -type f -print | sort
printf '\n--- template rendering and error-handler references ---\n'
rg -n -C 3 'render_template|TemplateNotFound|errorhandler|templates/' src testsRepository: williaby/ledgerbase Length of output: 5798 Resolve templates from the repository root.
🤖 Prompt for AI Agents |
||
| template_dir = project_root / "templates" | ||
|
Comment on lines
+41
to
+42
|
||
|
|
||
| if not os.path.isdir(template_dir): | ||
| if not template_dir.is_dir(): | ||
| print(f"Warning: Template directory not found at {template_dir}") | ||
| app = Flask(__name__) | ||
| else: | ||
| app = Flask(__name__, template_folder=template_dir) | ||
| app = Flask(__name__, template_folder=str(template_dir)) | ||
|
|
||
| app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv( | ||
| "DATABASE_URL", "sqlite:///default.db" | ||
| "DATABASE_URL", | ||
| "sqlite:///default.db", | ||
| ) | ||
| if not app.config["SQLALCHEMY_DATABASE_URI"]: | ||
| raise ValueError("DATABASE_URL environment variable is not set.") | ||
| raise ValueError(_DATABASE_URI_UNSET_MSG) | ||
|
|
||
| app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False | ||
| # app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", "default-secret-key") | ||
|
|
||
| # Initialize core services and middleware | ||
| db.init_app(app) | ||
|
|
@@ -61,11 +64,11 @@ | |
| register_error_handlers(app) | ||
|
|
||
| @app.route("/") | ||
| def index() -> str: | ||
|
Check warning on line 67 in src/ledgerbase/__init__.py
|
||
| return "LedgerBase API is running." | ||
|
|
||
| @app.route("/debug-sentry") | ||
| def trigger_error() -> str: | ||
|
Check warning on line 71 in src/ledgerbase/__init__.py
|
||
| result = 1 / 0 | ||
| return f"This should never return. Result was {result}" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,69 @@ | ||||||
| """Unit tests for the LedgerBase application factory.""" | ||||||
|
|
||||||
| from pathlib import Path | ||||||
|
|
||||||
| import pytest | ||||||
|
|
||||||
| import ledgerbase | ||||||
|
|
||||||
| HTTP_OK = 200 | ||||||
|
|
||||||
|
|
||||||
| def test_create_app_returns_configured_app(monkeypatch: pytest.MonkeyPatch) -> None: | ||||||
| """create_app returns a Flask app wired to the configured database URI.""" | ||||||
| monkeypatch.setenv("DATABASE_URL", "sqlite:///unit-test.db") | ||||||
| app = ledgerbase.create_app() | ||||||
| assert app.config["SQLALCHEMY_DATABASE_URI"] == "sqlite:///unit-test.db" | ||||||
| assert app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] is False | ||||||
|
|
||||||
|
|
||||||
| def test_create_app_defaults_the_database_uri( | ||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||
| ) -> None: | ||||||
| """create_app falls back to a local SQLite database when DATABASE_URL is unset.""" | ||||||
| monkeypatch.delenv("DATABASE_URL", raising=False) | ||||||
| app = ledgerbase.create_app() | ||||||
| assert app.config["SQLALCHEMY_DATABASE_URI"] == "sqlite:///default.db" | ||||||
|
|
||||||
|
|
||||||
| def test_create_app_rejects_empty_database_url( | ||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||
| ) -> None: | ||||||
| """An empty DATABASE_URL is rejected rather than silently accepted.""" | ||||||
| monkeypatch.setenv("DATABASE_URL", "") | ||||||
| with pytest.raises(ValueError, match="DATABASE_URL"): | ||||||
| ledgerbase.create_app() | ||||||
|
|
||||||
|
|
||||||
| def test_create_app_uses_template_dir_when_present( | ||||||
| monkeypatch: pytest.MonkeyPatch, | ||||||
| ) -> None: | ||||||
| """The sibling templates directory is used as the Jinja search path.""" | ||||||
| monkeypatch.setattr(Path, "is_dir", lambda _self: True) | ||||||
| app = ledgerbase.create_app() | ||||||
| assert app.template_folder is not None | ||||||
| assert app.template_folder.endswith("templates") | ||||||
|
|
||||||
|
Comment on lines
+38
to
+46
|
||||||
|
|
||||||
| def test_create_app_registers_core_routes() -> None: | ||||||
| """create_app wires the index, login, and debug-sentry routes.""" | ||||||
| app = ledgerbase.create_app() | ||||||
| rules = {rule.rule for rule in app.url_map.iter_rules()} | ||||||
| assert {"/", "/login", "/debug-sentry"} <= rules | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Remove the unsupported
Proposed test fix- assert {"/", "/login", "/debug-sentry"} <= rules
+ assert {"/", "/debug-sentry"} <= rules📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
|
|
||||||
| def test_index_route_responds() -> None: | ||||||
| """The index route reports that the API is running.""" | ||||||
| app = ledgerbase.create_app() | ||||||
| app.config.update(TESTING=True) | ||||||
| response = app.test_client().get("/") | ||||||
| assert response.status_code == HTTP_OK | ||||||
| assert b"LedgerBase API is running." in response.data | ||||||
|
|
||||||
|
|
||||||
| def test_debug_sentry_route_raises() -> None: | ||||||
| """The debug-sentry route deliberately raises a division error.""" | ||||||
| app = ledgerbase.create_app() | ||||||
| app.config.update(TESTING=True) | ||||||
| with pytest.raises(ZeroDivisionError): | ||||||
| app.test_client().get("/debug-sentry") | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,12 +1,67 @@ | ||
| """Unit tests for the configuration module.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from ledgerbase import config | ||
|
|
||
|
|
||
| def test_config_default() -> None: | ||
| """Test that the `config` module has the expected `__name__` attribute. | ||
| def test_config_module_loads() -> None: | ||
| """The config module exposes the expected public surface.""" | ||
| assert hasattr(config, "Config") | ||
| assert hasattr(config, "DevelopmentConfig") | ||
| assert hasattr(config, "ProductionConfig") | ||
|
|
||
|
|
||
| def test_base_config_defaults() -> None: | ||
| """The base Config disables SQLAlchemy modification tracking.""" | ||
| assert config.Config.SQLALCHEMY_TRACK_MODIFICATIONS is False | ||
| assert config.Config.SECRET_KEY | ||
|
|
||
|
|
||
| def test_development_config_enables_debug() -> None: | ||
| """DevelopmentConfig turns debug on.""" | ||
| assert config.DevelopmentConfig.DEBUG is True | ||
|
|
||
|
|
||
| def test_production_config_hardened() -> None: | ||
| """ProductionConfig turns debug off and forces secure cookies over HTTPS.""" | ||
| assert config.ProductionConfig.DEBUG is False | ||
| assert config.ProductionConfig.SESSION_COOKIE_SECURE is True | ||
| assert config.ProductionConfig.PREFERRED_URL_SCHEME == "https" | ||
|
|
||
|
|
||
| def test_get_security_settings() -> None: | ||
| """get_security_settings mirrors the hardened production values.""" | ||
| settings = config.get_security_settings() | ||
| assert settings == { | ||
| "SESSION_COOKIE_SECURE": True, | ||
| "PREFERRED_URL_SCHEME": "https", | ||
| } | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("env", "expected"), | ||
| [ | ||
| ("development", config.DevelopmentConfig), | ||
| ("production", config.ProductionConfig), | ||
| ("PRODUCTION", config.ProductionConfig), | ||
| ("nonsense", config.DevelopmentConfig), | ||
| ], | ||
| ) | ||
| def test_get_config_explicit_env(env: str, expected: type) -> None: | ||
| """get_config maps an explicit environment name case-insensitively.""" | ||
| assert config.get_config(env) is expected | ||
|
|
||
|
|
||
| def test_get_config_reads_flask_env(monkeypatch: pytest.MonkeyPatch) -> None: | ||
| """get_config falls back to FLASK_ENV when no argument is given.""" | ||
| monkeypatch.setenv("FLASK_ENV", "production") | ||
| assert config.get_config() is config.ProductionConfig | ||
|
|
||
|
|
||
| This ensures that the `config` module is properly loaded and accessible. | ||
| """ | ||
| if not hasattr(config, "__name__"): | ||
| pytest.fail("The `config` module should have a `__name__` attribute.") | ||
| def test_get_config_defaults_to_development( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| """get_config defaults to development when FLASK_ENV is unset.""" | ||
| monkeypatch.delenv("FLASK_ENV", raising=False) | ||
| assert config.get_config() is config.DevelopmentConfig |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| """Shared pytest fixtures for the LedgerBase test suite.""" | ||
|
|
||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
|
|
||
| from flask import Flask | ||
| from ledgerbase.error_handlers import register_error_handlers | ||
|
|
||
| REPO_ROOT = Path(__file__).resolve().parent.parent | ||
| TEMPLATE_DIR = REPO_ROOT / "templates" | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def app() -> Flask: | ||
| """Return a minimal Flask app wired to the repository template directory.""" | ||
| flask_app = Flask(__name__, template_folder=str(TEMPLATE_DIR)) | ||
| flask_app.config.update(TESTING=True) | ||
| register_error_handlers(flask_app) | ||
|
|
||
| @flask_app.route("/boom") | ||
| def boom() -> str: | ||
| raise RuntimeError | ||
|
|
||
| return flask_app | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def client(app: Flask): # noqa: ANN201 | ||
| """Return a test client for the minimal Flask app.""" | ||
| return app.test_client() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: williaby/ledgerbase
Length of output: 1108
🏁 Script executed:
Repository: williaby/ledgerbase
Length of output: 9302
🌐 Web query:
uv documentation tool.uv.package false project installation behavior💡 Result:
In uv, the
tool.uv.packagesetting determines whether a project is treated as a package to be built and installed into the virtual environment during operations likeuv syncoruv run[1][2]. Whentool.uv.packageis set tofalse, uv will not build or install the project itself into the project environment; it will only install its dependencies [1][2]. This is useful for "virtual" projects where you want to manage dependencies but do not intend to install the project source code as a package [3][4]. Key behaviors to note: 1. Implicit vs. Explicit Actions: Thetool.uv.package = falsesetting specifically toggles implicit builds during standard operations likeuv syncanduv run[5]. It does not disable explicit build commands [1][2]. 2. Explicit Builds: Even iftool.uv.packageis set tofalse, runninguv buildremains an explicit instruction to build the package, and uv will proceed with the build regardless of this setting [5][2]. 3. Dependency Management: In the context ofpathorworkspacedependencies, settingpackage = falsein the dependency source definition prevents that dependency from being built and installed, allowing it to be treated as a virtual dependency [4]. Conversely, this can be overridden by explicitly settingpackage = true[4]. In summary,tool.uv.package = falseensures that the project remains "virtual" during standard dependency resolution and environment synchronization, preventing unnecessary rebuilds of the project source code [1][5][6].Citations:
🏁 Script executed:
Repository: williaby/ledgerbase
Length of output: 50375
Enable installation of the project.
tool.uv.package = falsepreventsuv syncfrom installingledgerbase. Thesrclayout then leavesledgerbaseunavailable to tests unless another path setup is used, which can causeModuleNotFoundError. Remove this setting or setpackage = true.🤖 Prompt for AI Agents