Skip to content

Add projects-by-org endpoint and pagination for organizations list#727

Merged
vprashrex merged 10 commits intomainfrom
feat/projects-by-org-and-pagination
Apr 1, 2026
Merged

Add projects-by-org endpoint and pagination for organizations list#727
vprashrex merged 10 commits intomainfrom
feat/projects-by-org-and-pagination

Conversation

@vprashrex
Copy link
Copy Markdown
Collaborator

@vprashrex vprashrex commented Mar 30, 2026

Issue: #733

Summary

  • Add GET /projects/organization/{org_id} endpoint with org validation
  • Add has_more pagination to organizations list endpoint
  • Add Swagger docs for list_by_org

Checklist

Before submitting a pull request, please ensure that you mark these task.

  • Ran fastapi run --reload app/main.py or docker compose up in the repository root and test.
  • If you've fixed a bug or added code that is tested and has test cases.

Notes

Please add here if any other information is required for the reviewer.

Summary by CodeRabbit

  • New Features

    • Added endpoint to list projects for a specific organization.
    • Organization list now supports validated paging and returns pagination metadata (includes has_more).
  • Bug Fixes

    • Accessing an inactive organization now returns a clear Forbidden (403) response.
  • Documentation

    • Updated API docs for organization and project listing endpoints, including pagination details.
  • Tests

    • Added tests for pagination metadata, projects-by-organization behavior, and organization validation.

- Add GET /projects/organization/{org_id} endpoint with org validation
- Add has_more pagination to organizations list endpoint
- Add Swagger docs for list_by_org
@vprashrex vprashrex requested a review from Ayush8923 March 30, 2026 15:11
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 30, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added pagination metadata (metadata.has_more) and stricter Query validation for organizations listing; introduced a SUPERUSER-protected endpoint to list projects by organization (with organization validation); changed inactive-organization error to return HTTP 403; updated docs and tests accordingly.

Changes

Cohort / File(s) Summary
Docs
backend/app/api/docs/organization/list.md, backend/app/api/docs/projects/list_by_org.md
Documented metadata.has_more in organizations pagination and added docs for the new projects-by-organization endpoint and its preconditions.
Organization route
backend/app/api/routes/organization.py
read_organizations now uses Query(0, ge=0) / Query(100, ge=1, le=100) for skip/limit, computes total count, returns list plus metadata: {"has_more": bool}, and added explicit APIResponse return annotations for several handlers.
Project route
backend/app/api/routes/project.py
Added GET /projects/organization/{org_id} endpoint guarded by Permission.SUPERUSER; validates organization via validate_organization and returns projects with APIResponse[List[ProjectPublic]].
Organization CRUD
backend/app/crud/organization.py
validate_organization now raises HTTPException(status_code=403, detail="Organization is not active") when an organization exists but is_active is false; not-found path remains 404.
Tests
backend/app/tests/api/routes/test_org.py, backend/app/tests/api/routes/test_project.py, backend/app/tests/crud/test_org.py
Added tests for organizations pagination has_more, projects-by-organization (200 / 404 / 403 cases), and validate_organization success / not-found / inactive scenarios.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Router as API Router
  participant Auth as Permission check
  participant CRUD as Organization CRUD
  participant DB as Database

  Client->>Router: GET /projects/organization/{org_id}
  Router->>Auth: enforce Permission.SUPERUSER
  Auth-->>Router: allowed
  Router->>CRUD: validate_organization(session, org_id)
  CRUD->>DB: SELECT organization WHERE id=org_id
  DB-->>CRUD: organization record (or none)
  alt organization not found
    CRUD-->>Router: HTTPException 404
    Router-->>Client: 404 Not Found
  else organization inactive
    CRUD-->>Router: HTTPException 403
    Router-->>Client: 403 Forbidden
  else valid organization
    Router->>DB: get_projects_by_organization(session, org_id)
    DB-->>Router: list of projects
    Router-->>Client: 200 OK with APIResponse(data=projects, metadata?)
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • nishika26
  • Ayush8923

Poem

🐰 I hopped through docs and routes tonight,

counted pages till has_more was right,
guarded projects with a SUPERUSER light,
an inactive org replied 403—polite,
tests now nibble bugs away by moonlight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the two main changes: adding a projects-by-organization endpoint and adding pagination (has_more field) to the organizations list endpoint.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/projects-by-org-and-pagination

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
backend/app/api/routes/organization.py (1)

30-34: Add return type hint to the function.

The function is missing a return type annotation. As per coding guidelines, all function parameters and return values should have type hints.

Suggested fix
 def read_organizations(
     session: SessionDep,
     skip: int = Query(0, ge=0),
     limit: int = Query(100, ge=1, le=100),
-):
+) -> APIResponse[List[OrganizationPublic]]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/organization.py` around lines 30 - 34, Add an explicit
return type annotation to the read_organizations function signature (def
read_organizations(...)) — e.g. annotate it as returning a list of your
organization response model like -> List[Organization] or ->
list[OrganizationRead] (match your project's schema/model name), and add the
necessary import from typing (List) or from your schemas/models module so the
signature is fully typed.
backend/app/api/routes/project.py (1)

126-129: Add return type hint to the function.

The function is missing a return type annotation.

Suggested fix
-def read_projects_by_organization(session: SessionDep, org_id: int):
+def read_projects_by_organization(session: SessionDep, org_id: int) -> APIResponse[List[ProjectPublic]]:
     validate_organization(session=session, org_id=org_id)

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code."

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

In `@backend/app/api/routes/project.py` around lines 126 - 129, Add a return type
annotation to the function read_projects_by_organization so it follows the
codebase typing guidelines; update its signature to include the expected return
type (the type returned by APIResponse.success_response, e.g., APIResponse or a
more specific generic/DTO type if available) while keeping the existing
parameters (session: SessionDep, org_id: int) unchanged and ensure any imports
for that return type are added if needed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/api/routes/project.py`:
- Around line 126-129: Fix two issues: in validate_organization replace the
incorrect raise HTTPException("Organization is not active") with a proper
HTTPException call that supplies an integer status_code and a detail string
(e.g., raise HTTPException(status_code=503, detail="Organization is not
active")) so the exception is constructed correctly; then update the endpoint
signature for read_projects_by_organization to include the return type hint ->
APIResponse[List[ProjectPublic]] (and add any needed imports for List and
ProjectPublic) so the function is properly typed.

---

Nitpick comments:
In `@backend/app/api/routes/organization.py`:
- Around line 30-34: Add an explicit return type annotation to the
read_organizations function signature (def read_organizations(...)) — e.g.
annotate it as returning a list of your organization response model like ->
List[Organization] or -> list[OrganizationRead] (match your project's
schema/model name), and add the necessary import from typing (List) or from your
schemas/models module so the signature is fully typed.

In `@backend/app/api/routes/project.py`:
- Around line 126-129: Add a return type annotation to the function
read_projects_by_organization so it follows the codebase typing guidelines;
update its signature to include the expected return type (the type returned by
APIResponse.success_response, e.g., APIResponse or a more specific generic/DTO
type if available) while keeping the existing parameters (session: SessionDep,
org_id: int) unchanged and ensure any imports for that return type are added if
needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a558bf72-494b-40cd-bbd0-2cb53c220a22

📥 Commits

Reviewing files that changed from the base of the PR and between e577369 and abd1fc8.

📒 Files selected for processing (4)
  • backend/app/api/docs/organization/list.md
  • backend/app/api/docs/projects/list_by_org.md
  • backend/app/api/routes/organization.py
  • backend/app/api/routes/project.py

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…organizations; update read_projects_by_organization response type
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/crud/organization.py (1)

51-55: ⚠️ Potential issue | 🟠 Major

Use a semantically correct HTTP status for inactive organizations.

Line 55 uses 503 Service Unavailable for an inactive organization, but 503 indicates transient infrastructure failure and may trigger client-side retries. An inactive organization is a business-state denial—use 403 Forbidden instead.

Suggested change
-        raise HTTPException(status_code=503, detail="Organization is not active")
+        raise HTTPException(status_code=403, detail="Organization is not active")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/crud/organization.py` around lines 51 - 55, Change the HTTP
response for inactive organizations from a transient infrastructure code to a
business-state denial: in the validate_organization flow (the block that checks
organization.is_active, e.g., in validate_organization or where org_id is
logged) replace the HTTPException status_code 503 with 403 (Forbidden) so
clients receive the correct semantic status for an inactive organization; keep
the existing log message but ensure the raised HTTPException uses
status_code=403 and an appropriate detail string like "Organization is not
active".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/app/crud/organization.py`:
- Around line 51-55: Change the HTTP response for inactive organizations from a
transient infrastructure code to a business-state denial: in the
validate_organization flow (the block that checks organization.is_active, e.g.,
in validate_organization or where org_id is logged) replace the HTTPException
status_code 503 with 403 (Forbidden) so clients receive the correct semantic
status for an inactive organization; keep the existing log message but ensure
the raised HTTPException uses status_code=403 and an appropriate detail string
like "Organization is not active".

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5490e757-e95f-447c-b44d-26cc9d0873c8

📥 Commits

Reviewing files that changed from the base of the PR and between abd1fc8 and 84ce2e8.

📒 Files selected for processing (2)
  • backend/app/api/routes/project.py
  • backend/app/crud/organization.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/app/api/routes/project.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
backend/app/tests/api/routes/test_project.py (1)

100-142: Add a permission-denial test for this new route.

Since this endpoint is protected, include at least one 401/403 test (e.g., no token or non-superuser token) to prevent permission regressions.

Based on learnings: Implement organization and project-level permissions.

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

In `@backend/app/tests/api/routes/test_project.py` around lines 100 - 142, Add a
permission-denial test for the protected route by creating a test function
(e.g., test_read_projects_by_organization_unauthorized) that calls
client.get(f"{settings.API_V1_STR}/projects/organization/{project.organization_id}")
without auth headers to assert a 401, and another variant that uses a
non-superuser token fixture (e.g., non_superuser_token_headers) to assert a 403;
reuse create_test_project(db) to create the project/organization and reference
client and settings.API_V1_STR so the test verifies both unauthenticated and
insufficient-permission responses for the endpoint.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/app/tests/api/routes/test_project.py`:
- Around line 118-125: In test_read_projects_by_organization_not_found, avoid
the hardcoded 999999 and the unused db param by computing a
guaranteed-nonexistent organization ID from the database (e.g., use the db
Session to query the maximum Organization.id and set non_existent_id = (max_id
or 0) + 1), then call
client.get(f"{settings.API_V1_STR}/projects/organization/{non_existent_id}",
headers=superuser_token_headers) and assert 404; update the function body of
test_read_projects_by_organization_not_found to use this computed id so the db
parameter is used and the test is deterministic.

In `@backend/app/tests/crud/test_org.py`:
- Around line 47-63: The tests test_validate_organization_not_found and
test_validate_organization_inactive currently only match exception text; update
them to also assert the HTTPException.status_code to prevent silent behavior
drift: use pytest.raises as a context manager to capture the exception from
validate_organization and add an assertion that excinfo.value.status_code == 404
for the "Organization not found" case and == 503 for the "Organization is not
active" case, referring to validate_organization and HTTPException to locate the
code.

---

Nitpick comments:
In `@backend/app/tests/api/routes/test_project.py`:
- Around line 100-142: Add a permission-denial test for the protected route by
creating a test function (e.g., test_read_projects_by_organization_unauthorized)
that calls
client.get(f"{settings.API_V1_STR}/projects/organization/{project.organization_id}")
without auth headers to assert a 401, and another variant that uses a
non-superuser token fixture (e.g., non_superuser_token_headers) to assert a 403;
reuse create_test_project(db) to create the project/organization and reference
client and settings.API_V1_STR so the test verifies both unauthenticated and
insufficient-permission responses for the endpoint.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d06caaa2-0482-4814-82f9-c8a517311875

📥 Commits

Reviewing files that changed from the base of the PR and between 84ce2e8 and 24641d6.

📒 Files selected for processing (3)
  • backend/app/tests/api/routes/test_org.py
  • backend/app/tests/api/routes/test_project.py
  • backend/app/tests/crud/test_org.py

Comment on lines +118 to +125
def test_read_projects_by_organization_not_found(
db: Session, superuser_token_headers: dict[str, str]
) -> None:
response = client.get(
f"{settings.API_V1_STR}/projects/organization/999999",
headers=superuser_token_headers,
)
assert response.status_code == 404
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

Replace hardcoded missing-org ID and fix unused db (Line 119).

Using 999999 is brittle; generate a guaranteed non-existent ID from the DB so the test stays deterministic and resolves Ruff ARG001.

Proposed fix
+from app.tests.utils.utils import get_non_existent_id
@@
 def test_read_projects_by_organization_not_found(
     db: Session, superuser_token_headers: dict[str, str]
 ) -> None:
+    non_existent_org_id = get_non_existent_id(db, Organization)
     response = client.get(
-        f"{settings.API_V1_STR}/projects/organization/999999",
+        f"{settings.API_V1_STR}/projects/organization/{non_existent_org_id}",
         headers=superuser_token_headers,
     )
     assert response.status_code == 404
🧰 Tools
🪛 Ruff (0.15.7)

[warning] 119-119: Unused function argument: db

(ARG001)

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

In `@backend/app/tests/api/routes/test_project.py` around lines 118 - 125, In
test_read_projects_by_organization_not_found, avoid the hardcoded 999999 and the
unused db param by computing a guaranteed-nonexistent organization ID from the
database (e.g., use the db Session to query the maximum Organization.id and set
non_existent_id = (max_id or 0) + 1), then call
client.get(f"{settings.API_V1_STR}/projects/organization/{non_existent_id}",
headers=superuser_token_headers) and assert 404; update the function body of
test_read_projects_by_organization_not_found to use this computed id so the db
parameter is used and the test is deterministic.

Comment on lines +47 to +63
def test_validate_organization_not_found(db: Session) -> None:
"""Test that validation fails when organization does not exist."""
non_existent_org_id = get_non_existent_id(db, Organization)
with pytest.raises(HTTPException, match="Organization not found"):
validate_organization(session=db, org_id=non_existent_org_id)


def test_validate_organization_inactive(db: Session) -> None:
"""Test that validation fails when organization is inactive."""
organization = create_test_organization(db)
organization.is_active = False
db.add(organization)
db.commit()
db.refresh(organization)

with pytest.raises(HTTPException, match="Organization is not active"):
validate_organization(session=db, org_id=organization.id)
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

Assert HTTP status codes, not only error text.

These tests currently pass on message match alone; they should also lock in 404 and 503 to prevent silent behavior drift.

Proposed test hardening
 def test_validate_organization_not_found(db: Session) -> None:
     """Test that validation fails when organization does not exist."""
     non_existent_org_id = get_non_existent_id(db, Organization)
-    with pytest.raises(HTTPException, match="Organization not found"):
+    with pytest.raises(HTTPException) as exc_info:
         validate_organization(session=db, org_id=non_existent_org_id)
+    assert exc_info.value.status_code == 404
+    assert exc_info.value.detail == "Organization not found"
@@
 def test_validate_organization_inactive(db: Session) -> None:
@@
-    with pytest.raises(HTTPException, match="Organization is not active"):
+    with pytest.raises(HTTPException) as exc_info:
         validate_organization(session=db, org_id=organization.id)
+    assert exc_info.value.status_code == 503
+    assert exc_info.value.detail == "Organization is not active"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_validate_organization_not_found(db: Session) -> None:
"""Test that validation fails when organization does not exist."""
non_existent_org_id = get_non_existent_id(db, Organization)
with pytest.raises(HTTPException, match="Organization not found"):
validate_organization(session=db, org_id=non_existent_org_id)
def test_validate_organization_inactive(db: Session) -> None:
"""Test that validation fails when organization is inactive."""
organization = create_test_organization(db)
organization.is_active = False
db.add(organization)
db.commit()
db.refresh(organization)
with pytest.raises(HTTPException, match="Organization is not active"):
validate_organization(session=db, org_id=organization.id)
def test_validate_organization_not_found(db: Session) -> None:
"""Test that validation fails when organization does not exist."""
non_existent_org_id = get_non_existent_id(db, Organization)
with pytest.raises(HTTPException) as exc_info:
validate_organization(session=db, org_id=non_existent_org_id)
assert exc_info.value.status_code == 404
assert exc_info.value.detail == "Organization not found"
def test_validate_organization_inactive(db: Session) -> None:
"""Test that validation fails when organization is inactive."""
organization = create_test_organization(db)
organization.is_active = False
db.add(organization)
db.commit()
db.refresh(organization)
with pytest.raises(HTTPException) as exc_info:
validate_organization(session=db, org_id=organization.id)
assert exc_info.value.status_code == 503
assert exc_info.value.detail == "Organization is not active"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/crud/test_org.py` around lines 47 - 63, The tests
test_validate_organization_not_found and test_validate_organization_inactive
currently only match exception text; update them to also assert the
HTTPException.status_code to prevent silent behavior drift: use pytest.raises as
a context manager to capture the exception from validate_organization and add an
assertion that excinfo.value.status_code == 404 for the "Organization not found"
case and == 503 for the "Organization is not active" case, referring to
validate_organization and HTTPException to locate the code.

@vprashrex vprashrex added the enhancement New feature or request label Apr 1, 2026
@vprashrex vprashrex requested a review from nishika26 April 1, 2026 09:48

return APIResponse.success_response(organizations)
has_more = (skip + limit) < count
return APIResponse.success_response(organizations, metadata={"has_more": has_more})
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this kind of support is not there in read projects endpoint as well so why are we only adding it to this one

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

since this is requried for frontend that's why

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

but how do you know that we wont require this for project in future

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

but do u think organization would have lot of projects? .. for consistency I added the has_more metadata in projects endpoint as well

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
backend/app/tests/api/routes/test_project.py (1)

117-124: ⚠️ Potential issue | 🟡 Minor

Replace hardcoded org ID and use db parameter in not-found test.

At Line 121, 999999 is brittle and Line 118 leaves db unused (Ruff ARG001). Prefer generating a guaranteed non-existent organization ID from DB state so the test remains deterministic.

Proposed fix
+from app.models import Organization
+from app.tests.utils.utils import get_non_existent_id
@@
 def test_read_projects_by_organization_not_found(
     db: Session, superuser_token_headers: dict[str, str]
 ) -> None:
+    non_existent_org_id = get_non_existent_id(db, Organization)
     response = client.get(
-        f"{settings.API_V1_STR}/projects/organization/999999",
+        f"{settings.API_V1_STR}/projects/organization/{non_existent_org_id}",
         headers=superuser_token_headers,
     )
     assert response.status_code == 404
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/tests/api/routes/test_project.py` around lines 117 - 124, In
test_read_projects_by_organization_not_found replace the hardcoded 999999 and
the unused db param by deriving a guaranteed non-existent organization id from
the current DB state: query the Organization id max via the test DB session
(db), compute non_existing_id = max_id + 1 (treat missing rows as 0), then use
that id in the GET URL and remove the unused hardcoded literal so db is actually
used; locate this logic in the test_read_projects_by_organization_not_found
function and update imports if needed to reference the Organization model or its
id column.
🧹 Nitpick comments (1)
backend/app/tests/api/routes/test_project.py (1)

99-141: Add an authorization regression test for non-superuser access.

The endpoint is security-sensitive and currently only exercised with superuser_token_headers. Add a test that a non-superuser token is denied (expected 403) to prevent permission regressions.

Based on learnings: "Implement organization and project-level permissions"

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

In `@backend/app/tests/api/routes/test_project.py` around lines 99 - 141, Add a
new regression test verifying non-superuser access is denied: create a test
named e.g. test_read_projects_by_organization_non_superuser_denied that mirrors
test_read_projects_by_organization but uses a non-superuser token header (e.g.,
normal_user_token_headers) and calls the same endpoint for
project.organization_id (or an organization created via
create_test_organization/create_test_project), then assert response.status_code
== 403; place it next to the existing tests (test_read_projects_by_organization,
test_read_projects_by_organization_not_found,
test_read_projects_by_inactive_organization) so permissions regressions are
caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@backend/app/tests/api/routes/test_project.py`:
- Around line 117-124: In test_read_projects_by_organization_not_found replace
the hardcoded 999999 and the unused db param by deriving a guaranteed
non-existent organization id from the current DB state: query the Organization
id max via the test DB session (db), compute non_existing_id = max_id + 1 (treat
missing rows as 0), then use that id in the GET URL and remove the unused
hardcoded literal so db is actually used; locate this logic in the
test_read_projects_by_organization_not_found function and update imports if
needed to reference the Organization model or its id column.

---

Nitpick comments:
In `@backend/app/tests/api/routes/test_project.py`:
- Around line 99-141: Add a new regression test verifying non-superuser access
is denied: create a test named e.g.
test_read_projects_by_organization_non_superuser_denied that mirrors
test_read_projects_by_organization but uses a non-superuser token header (e.g.,
normal_user_token_headers) and calls the same endpoint for
project.organization_id (or an organization created via
create_test_organization/create_test_project), then assert response.status_code
== 403; place it next to the existing tests (test_read_projects_by_organization,
test_read_projects_by_organization_not_found,
test_read_projects_by_inactive_organization) so permissions regressions are
caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3ac11f2a-002b-45c2-a983-27a18d9d1430

📥 Commits

Reviewing files that changed from the base of the PR and between 5d75b34 and 8e14322.

📒 Files selected for processing (3)
  • backend/app/api/routes/organization.py
  • backend/app/crud/organization.py
  • backend/app/tests/api/routes/test_project.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/app/crud/organization.py
  • backend/app/api/routes/organization.py

@vprashrex vprashrex requested a review from nishika26 April 1, 2026 10:46
@vprashrex vprashrex merged commit 7d310ce into main Apr 1, 2026
2 checks passed
@vprashrex vprashrex deleted the feat/projects-by-org-and-pagination branch April 1, 2026 11:02
@github-project-automation github-project-automation bot moved this to Closed in Kaapi-dev Apr 1, 2026
@vprashrex vprashrex linked an issue Apr 1, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Closed

Development

Successfully merging this pull request may close these issues.

API: Fetch projects by organization

5 participants