fix(jira): use model_validator(after) for jira_token_set + timeout on test
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
FastAPI uses __pydantic_validator__.validate_python() which bypasses model_validate() overrides. Switch to @model_validator(mode='after') which the Pydantic Rust core always calls, so jira_token_set is now correctly derived from the excluded jira_api_token field. Also add a 10s timeout to the jira-test endpoint and better error messages (the Atlassian library's "Expecting value" JSON error was ambiguous). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -295,7 +295,8 @@ def test_jira_connection(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
jira = get_user_jira_client(current_user, db)
|
jira = get_user_jira_client(current_user, db)
|
||||||
# Lightweight call: get current user info
|
# Lightweight call: get current user info (10 s hard timeout)
|
||||||
|
jira._session.timeout = 10 # type: ignore[attr-defined]
|
||||||
myself = jira.myself()
|
myself = jira.myself()
|
||||||
return {
|
return {
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
@@ -303,10 +304,22 @@ def test_jira_connection(
|
|||||||
"jira_url": jira_url,
|
"jira_url": jira_url,
|
||||||
}
|
}
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise HTTPException(
|
err = str(exc)
|
||||||
status_code=502,
|
# Translate common Atlassian-library errors into human-readable messages
|
||||||
detail=f"Jira connection failed: {exc}",
|
if "Expecting value" in err or "line 1 column 1" in err:
|
||||||
)
|
detail = (
|
||||||
|
"Jira returned an unexpected response — check that the URL is correct "
|
||||||
|
"and that the account email + API token are valid."
|
||||||
|
)
|
||||||
|
elif "401" in err or "Unauthorized" in err:
|
||||||
|
detail = "Authentication failed (401). Verify your Atlassian email and API token."
|
||||||
|
elif "403" in err or "Forbidden" in err:
|
||||||
|
detail = "Access denied (403). The token may not have permission to access this Jira instance."
|
||||||
|
elif "timed out" in err.lower() or "timeout" in err.lower():
|
||||||
|
detail = "Connection timed out. Check the Jira URL is reachable from the server."
|
||||||
|
else:
|
||||||
|
detail = f"Jira connection failed: {err}"
|
||||||
|
raise HTTPException(status_code=502, detail=detail)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import re
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, EmailStr, field_validator
|
from pydantic import BaseModel, ConfigDict, EmailStr, Field, field_validator, model_validator
|
||||||
|
|
||||||
|
|
||||||
# ── Username policy ─────────────────────────────────────────────────
|
# ── Username policy ─────────────────────────────────────────────────
|
||||||
@@ -148,15 +148,20 @@ class UserOut(BaseModel):
|
|||||||
notification_preferences: dict | None = None
|
notification_preferences: dict | None = None
|
||||||
jira_account_id: str | None = None
|
jira_account_id: str | None = None
|
||||||
jira_email: str | None = None
|
jira_email: str | None = None
|
||||||
# Never return the raw token — just indicate whether it is configured.
|
# Read from ORM but NEVER exposed in responses — used only to derive jira_token_set.
|
||||||
|
jira_api_token: str | None = Field(default=None, exclude=True)
|
||||||
|
# True when the user has a personal Atlassian token stored.
|
||||||
jira_token_set: bool = False
|
jira_token_set: bool = False
|
||||||
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
@classmethod
|
@model_validator(mode="after")
|
||||||
def model_validate(cls, obj, *args, **kwargs): # type: ignore[override]
|
def _derive_jira_token_set(self) -> "UserOut":
|
||||||
instance = super().model_validate(obj, *args, **kwargs)
|
"""Set jira_token_set from the (excluded) jira_api_token field.
|
||||||
# Derive jira_token_set from the ORM object without exposing the value
|
|
||||||
if hasattr(obj, "jira_api_token"):
|
Uses @model_validator(mode='after') so Pydantic's Rust core calls it
|
||||||
instance.jira_token_set = bool(obj.jira_api_token)
|
during FastAPI response serialisation — model_validate() overrides are
|
||||||
return instance
|
bypassed by FastAPI's __pydantic_validator__.validate_python() path.
|
||||||
|
"""
|
||||||
|
self.jira_token_set = bool(self.jira_api_token)
|
||||||
|
return self
|
||||||
|
|||||||
Reference in New Issue
Block a user