Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
f3a5c93
test: improve code coverage from 40% to 52% with 341 new tests
JohnRDOrazio Apr 7, 2026
61e11cb
fix: resolve CI failures in test suite
JohnRDOrazio Apr 7, 2026
d43bf93
test: pass from_ref='main' to all create_branch calls in git tests
JohnRDOrazio Apr 7, 2026
5795356
fix: replace deprecated datetime.utcnow() with timezone-aware datetim…
JohnRDOrazio Apr 7, 2026
fe6c43c
fix: use async overrides for auth dependencies in authed_client fixture
JohnRDOrazio Apr 7, 2026
9721cc9
test: use assert_awaited assertions for AsyncMock verification
JohnRDOrazio Apr 7, 2026
221d289
test: exercise service.get() in anonymous access test instead of _to_…
JohnRDOrazio Apr 7, 2026
b53dd35
test: use ValidationError instead of Exception, remove file-wide noqa
JohnRDOrazio Apr 7, 2026
8ff71eb
test: extract notification service mock into reusable fixture
JohnRDOrazio Apr 7, 2026
4b63ecf
test: wrap dependency overrides in try/finally for reliable cleanup
JohnRDOrazio Apr 7, 2026
e18340c
test: rename test_caches_instance to reflect actual behavior
JohnRDOrazio Apr 7, 2026
d12ba65
fix: use consistent sort flags for all git history walk paths
JohnRDOrazio Apr 7, 2026
66cb78c
test: move local imports to module level in lint route tests
JohnRDOrazio Apr 7, 2026
7f646e4
test: replace file-wide noqa with targeted per-parameter suppressions
JohnRDOrazio Apr 7, 2026
a1d27d1
test: fix setattr return value error in test_user_settings_routes.py
JohnRDOrazio Apr 7, 2026
d5bc75f
test: add missing type parameters in test_user_service.py
JohnRDOrazio Apr 7, 2026
28f56aa
test: add missing type parameters in test_github_service.py
JohnRDOrazio Apr 7, 2026
d01706a
test: fix dict type in test_join_request_service.py
JohnRDOrazio Apr 7, 2026
d3b10d1
test: add missing type parameters in test_worker.py
JohnRDOrazio Apr 7, 2026
93913b8
test: remove unused type: ignore comments in test_beacon_token.py
JohnRDOrazio Apr 7, 2026
9802927
test: fix mypy index errors in test_linter.py
JohnRDOrazio Apr 7, 2026
387bb48
test: fix mypy arg-type errors in test_search.py
JohnRDOrazio Apr 7, 2026
76d8567
test: add missing type annotations in test_auth.py
JohnRDOrazio Apr 7, 2026
c3cc74e
test: use sync Mock for verify_webhook_signature fixture
JohnRDOrazio Apr 7, 2026
bac5a46
test: assert exact entity count in ontology index reindex test
JohnRDOrazio Apr 7, 2026
8103b0c
test: use assert_awaited_once_with for async mock verification
JohnRDOrazio Apr 7, 2026
d14fd57
test: use assert_awaited for async mock verification in project servi…
JohnRDOrazio Apr 7, 2026
1f37c09
test: use distinct admin user in test_cannot_remove_owner
JohnRDOrazio Apr 7, 2026
b4af992
test: improve project service test assertions and coverage
JohnRDOrazio Apr 7, 2026
6489a07
test: improve project service test quality and coverage
JohnRDOrazio Apr 7, 2026
6b1a1c7
refactor: migrate Pydantic models from class Config to ConfigDict
JohnRDOrazio Apr 7, 2026
2ec07b8
fix: eliminate all test warnings
JohnRDOrazio Apr 7, 2026
5c22ac1
fix: eliminate RuntimeWarning by using MagicMock for unused DB session
JohnRDOrazio Apr 7, 2026
78a596f
chore: exclude deprecated git/repository.py from coverage
JohnRDOrazio Apr 7, 2026
3a3009e
test: increase coverage from 54% to 65% with 185 new tests
JohnRDOrazio Apr 7, 2026
a3cda70
test: add unit tests for routes, services, and git layer
JohnRDOrazio Apr 8, 2026
658f579
test: increase coverage from 72% to 78% with 122 new tests
JohnRDOrazio Apr 8, 2026
b66e5e6
fix: address code review findings across tests and routes
JohnRDOrazio Apr 8, 2026
0e56b34
test: add 35 tests for pull_request_service to reach 80% coverage
JohnRDOrazio Apr 8, 2026
ea286f9
fix: address second round of code review findings
JohnRDOrazio Apr 8, 2026
154f9e4
fix: strengthen test assertions per code review
JohnRDOrazio Apr 8, 2026
7b9eb89
refactor: extract _simulate_refresh into shared factory and add db.ad…
JohnRDOrazio Apr 8, 2026
d1e1387
test: tighten indexed_ontology assertions and add index query failure…
JohnRDOrazio Apr 8, 2026
236e2d9
docs: explain inline-import patch target for get_user_service
JohnRDOrazio Apr 8, 2026
59ccddc
fix: address code review — token literal, exact assertions, serialize…
JohnRDOrazio Apr 8, 2026
9cfca0d
test: add route-level tests for pull_requests, lint, and projects
JohnRDOrazio Apr 8, 2026
b221058
test: bring key services and worker to 80%+ coverage
JohnRDOrazio Apr 8, 2026
c0d4ea4
test: bring bare_repository and projects routes to 80%+ coverage
JohnRDOrazio Apr 8, 2026
f1bf326
fix: address code review findings in extractor and routes tests
JohnRDOrazio Apr 8, 2026
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: 27 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Distribution

on:
push:
branches-ignore: [renovate/**]
branches: [main, dev]
tags: [ontokit-*]
pull_request:

Expand All @@ -23,6 +23,32 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: read
services:
postgres:
image: pgvector/pgvector:pg17
env:
POSTGRES_USER: ontokit_test
POSTGRES_PASSWORD: ontokit_test
POSTGRES_DB: ontokit_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
redis:
image: redis:7
ports:
- 6379:6379
options: >-
--health-cmd "redis-cli ping"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
DATABASE_URL: postgresql+asyncpg://ontokit_test:ontokit_test@localhost:5432/ontokit_test
REDIS_URL: redis://localhost:6379/0
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v4
Expand Down
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ repos:
- sentence-transformers>=3.0.0
- cryptography>=42.0.0
- pytest>=8.0.0
- pytest-asyncio>=0.24.0
62 changes: 62 additions & 0 deletions docs/coverage-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Test Coverage Plan: 78% → 80%

**Created:** 2026-04-08
**Updated:** 2026-04-08
**Baseline:** 78% (7502/9571 statements covered, 983 tests)
**Target:** 80% (7657 statements covered, ~155 more needed)

## Completed

The following Phase 1 items have been completed:

| File | Before | After | Tests Added |
|------|--------|-------|-------------|
| `services/project_service.py` | 55% | 94% | ~40 |
| `services/suggestion_service.py` | 39% | 96% | ~51 |
| `services/embedding_service.py` | 33% | 99% | ~33 |

## Phase 1 — Remaining (~170 statements recoverable)

| File | Current | Missed | Target | To Recover |
|------|---------|--------|--------|------------|
| `services/pull_request_service.py` | 56% | 305 | 80% | ~170 |

### pull_request_service.py (56% → 80%)
- [ ] `create_pull_request()` — creation with validation
- [ ] `merge_pull_request()` — merge strategies
- [ ] `close_pull_request()`, `reopen_pull_request()`
- [ ] Review CRUD: `create_review()`, `list_reviews()`
- [ ] Comment CRUD: `create_comment()`, `list_comments()`, `update_comment()`, `delete_comment()`
- [ ] Branch management: `list_branches()`, `create_branch()`
- [ ] GitHub integration: `create_github_integration()`, `update_github_integration()`, `delete_github_integration()`
- [ ] Webhook handlers: `handle_github_pr_webhook()`, `handle_github_review_webhook()`, `handle_github_push_webhook()`
- [ ] PR settings: `get_pr_settings()`, `update_pr_settings()`

Covering ~155 of the 305 missed statements reaches 80% overall.
Comment on lines +18 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Reconcile inconsistent statement recovery targets.

Lines 18 and 22 claim "~170 statements recoverable" for the remaining Phase 1 work, but line 35 states "Covering ~155 of the 305 missed statements reaches 80% overall," which aligns with the overall gap of ~155 from line 6. This discrepancy creates ambiguity about the actual work needed.

Please clarify whether:

  • ~170 represents a stretch goal beyond 80%, or
  • ~155 is the precise target and lines 18/22 should be updated accordingly.
🧰 Tools
🪛 LanguageTool

[uncategorized] ~31-~31: The official name of this software platform is spelled with a capital “H”.
Context: ...ate_branch()- [ ] GitHub integration:create_github_integration(), update_github_integrat...

(GITHUB)


[uncategorized] ~31-~31: The official name of this software platform is spelled with a capital “H”.
Context: ...gration: create_github_integration(), update_github_integration(), `delete_github_integrat...

(GITHUB)


[uncategorized] ~32-~32: The official name of this software platform is spelled with a capital “H”.
Context: ..._integration()- [ ] Webhook handlers:handle_github_pr_webhook(), handle_github_review_we...

(GITHUB)


[uncategorized] ~32-~32: The official name of this software platform is spelled with a capital “H”.
Context: ...handlers: handle_github_pr_webhook(), handle_github_review_webhook(), `handle_github_push_...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/coverage-plan.md` around lines 18 - 35, The document has inconsistent
recovery counts for Phase 1: "~170 statements recoverable" vs "Covering ~155 of
the 305 missed statements reaches 80%"; update the Phase 1 text so the two
numbers match and clarify intent—either change the "~170" mentions to "~155" to
reflect the precise 80% target, or explicitly label "~170" as a stretch goal
(e.g., "stretch goal: ~170 statements") and keep "~155" as the required target;
make the change near the Phase 1 heading and the pull_request_service.py bullets
to ensure both the summary and the table match the chosen value.


## Phase 2 — Medium Impact (~250 statements)

| File | Current | Missed | Target | To Recover |
|------|---------|--------|--------|------------|
| `git/bare_repository.py` | 70% | 150 | 80% | ~55 |
| `worker.py` | 70% | 111 | 80% | ~40 |
| `services/ontology_extractor.py` | 64% | 93 | 80% | ~45 |
| `services/ontology_index.py` | 75% | 89 | 80% | ~25 |
| `services/github_sync.py` | 61% | 46 | 80% | ~25 |
| `services/indexed_ontology.py` | 44% | 50 | 80% | ~30 |
| `services/normalization_service.py` | 73% | 25 | 80% | ~10 |
| `services/embedding_providers/*` | 0-75% | ~108 | 80% | ~20 |

## Phase 3 — Diminishing Returns

| File | Current | Notes |
|------|---------|-------|
| `main.py` | 54% | Startup/lifespan — hard to unit test |
| `runner.py` | 0% | 6 lines, CLI entry point |
| `services/ontology.py` | 82% | Already above target |
| `services/linter.py` | 80% | Already at target |

## Execution Order

1. `pull_request_service.py` — the only Phase 1 item remaining; ~155 statements gets us to 80%
2. Phase 2 files as needed to build further margin
9 changes: 7 additions & 2 deletions ontokit/api/routes/projects.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,11 @@ async def _ensure_ontology_loaded(
if git is not None and git.repository_exists(project_id):
try:
await ontology.load_from_git(project_id, branch, filename, git)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=str(e),
) from e
except Exception as e:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
Expand All @@ -556,7 +561,7 @@ async def _ensure_ontology_loaded(
) from e
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=str(e),
) from e

Expand Down Expand Up @@ -1274,7 +1279,7 @@ async def save_source_content(
g.parse(data=data.content, format="turtle")
except Exception as e:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=f"Invalid Turtle syntax: {e}",
) from e

Expand Down
10 changes: 5 additions & 5 deletions ontokit/collab/presence.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""User presence tracking for collaboration sessions."""

from datetime import datetime, timedelta
from datetime import UTC, datetime, timedelta

from ontokit.collab.protocol import User

Expand Down Expand Up @@ -37,7 +37,7 @@ def join(self, room: str, user: User) -> list[User]:
user.color = self._colors[user_count % len(self._colors)]

self._rooms[room][user.user_id] = user
self._last_seen[user.user_id] = datetime.utcnow()
self._last_seen[user.user_id] = datetime.now(tz=UTC)

return list(self._rooms[room].values())

Expand All @@ -57,23 +57,23 @@ def update_cursor(self, room: str, user_id: str, path: str) -> None:
"""Update user's cursor position."""
if room in self._rooms and user_id in self._rooms[room]:
self._rooms[room][user_id].cursor_path = path
self._last_seen[user_id] = datetime.utcnow()
self._last_seen[user_id] = datetime.now(tz=UTC)

def get_users(self, room: str) -> list[User]:
"""Get all users in a room."""
return list(self._rooms.get(room, {}).values())

def heartbeat(self, user_id: str) -> None:
"""Update last seen timestamp for a user."""
self._last_seen[user_id] = datetime.utcnow()
self._last_seen[user_id] = datetime.now(tz=UTC)

def cleanup_stale(self, timeout_minutes: int = 5) -> list[tuple[str, str]]:
"""
Remove users who haven't been seen recently.

Returns list of (room, user_id) tuples for removed users.
"""
cutoff = datetime.utcnow() - timedelta(minutes=timeout_minutes)
cutoff = datetime.now(tz=UTC) - timedelta(minutes=timeout_minutes)
removed = []

for room, users in list(self._rooms.items()):
Expand Down
20 changes: 14 additions & 6 deletions ontokit/git/bare_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def _resolve_ref(self, ref: str) -> pygit2.Commit:

# Try as partial hash
try:
for commit in self.repo.walk(self.repo.head.target, pygit2.GIT_SORT_TIME): # type: ignore[arg-type]
for commit in self.repo.walk(self.repo.head.target, pygit2.enums.SortMode.TIME):
if str(commit.id).startswith(ref):
return commit
except Exception:
Expand Down Expand Up @@ -361,7 +361,10 @@ def get_history(
for ref_name in self.repo.references:
if ref_name.startswith("refs/heads/"):
ref = self.repo.references[ref_name]
for commit in self.repo.walk(ref.target, pygit2.GIT_SORT_TIME): # type: ignore[arg-type]
for commit in self.repo.walk(
ref.target,
pygit2.enums.SortMode.TIME | pygit2.enums.SortMode.TOPOLOGICAL,
):
commit_hash = str(commit.id)
if commit_hash not in seen_hashes:
seen_hashes.add(commit_hash)
Expand All @@ -379,7 +382,12 @@ def get_history(
target = self.repo.head.target

commit_iter = []
for count, commit in enumerate(self.repo.walk(target, pygit2.GIT_SORT_TIME)): # type: ignore[arg-type]
for count, commit in enumerate(
self.repo.walk(
target,
pygit2.enums.SortMode.TIME | pygit2.enums.SortMode.TOPOLOGICAL,
)
):
commit_iter.append(commit)
if count + 1 >= limit:
break
Expand Down Expand Up @@ -746,12 +754,12 @@ def get_commits_between(self, from_ref: str, to_ref: str = "HEAD") -> list[Commi

# Get commits reachable from to_ref but not from from_ref
from_ancestors = set()
for commit in self.repo.walk(from_commit.id, pygit2.GIT_SORT_TIME): # type: ignore[arg-type]
for commit in self.repo.walk(from_commit.id, pygit2.enums.SortMode.TIME):
from_ancestors.add(str(commit.id))

for commit in self.repo.walk(to_commit.id, pygit2.GIT_SORT_TIME): # type: ignore[arg-type]
for commit in self.repo.walk(to_commit.id, pygit2.enums.SortMode.TIME):
if str(commit.id) in from_ancestors:
break
continue
commits.append(self._commit_to_info(commit))

except Exception:
Expand Down
Loading
Loading