merge testing->tomerge/main #7
@@ -24,7 +24,9 @@ class DecnetUser(HttpUser):
|
|||||||
wait_time = between(0.01, 0.05) # near-zero think time — max pressure
|
wait_time = between(0.01, 0.05) # near-zero think time — max pressure
|
||||||
|
|
||||||
def _login_with_retry(self):
|
def _login_with_retry(self):
|
||||||
"""Login with exponential backoff — handles connection storms."""
|
"""Login with exponential backoff — handles connection storms.
|
||||||
|
|
||||||
|
Returns (access_token, must_change_password)."""
|
||||||
for attempt in range(_MAX_LOGIN_RETRIES):
|
for attempt in range(_MAX_LOGIN_RETRIES):
|
||||||
resp = self.client.post(
|
resp = self.client.post(
|
||||||
"/api/v1/auth/login",
|
"/api/v1/auth/login",
|
||||||
@@ -32,7 +34,8 @@ class DecnetUser(HttpUser):
|
|||||||
name="/api/v1/auth/login [on_start]",
|
name="/api/v1/auth/login [on_start]",
|
||||||
)
|
)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
return resp.json()["access_token"]
|
body = resp.json()
|
||||||
|
return body["access_token"], bool(body.get("must_change_password", False))
|
||||||
# Status 0 = connection refused, retry with backoff
|
# Status 0 = connection refused, retry with backoff
|
||||||
if resp.status_code == 0 or resp.status_code >= 500:
|
if resp.status_code == 0 or resp.status_code >= 500:
|
||||||
time.sleep(_LOGIN_BACKOFF_BASE * (2 ** attempt))
|
time.sleep(_LOGIN_BACKOFF_BASE * (2 ** attempt))
|
||||||
@@ -41,16 +44,20 @@ class DecnetUser(HttpUser):
|
|||||||
raise RuntimeError(f"Login failed after {_MAX_LOGIN_RETRIES} retries (last status: {resp.status_code})")
|
raise RuntimeError(f"Login failed after {_MAX_LOGIN_RETRIES} retries (last status: {resp.status_code})")
|
||||||
|
|
||||||
def on_start(self):
|
def on_start(self):
|
||||||
token = self._login_with_retry()
|
token, must_change = self._login_with_retry()
|
||||||
|
|
||||||
# Clear must_change_password
|
# Only pay the change-password + re-login cost on the very first run
|
||||||
self.client.post(
|
# against a fresh DB. Every run after that, must_change_password is
|
||||||
"/api/v1/auth/change-password",
|
# already False — skip it or the login path becomes a bcrypt storm.
|
||||||
json={"old_password": ADMIN_PASS, "new_password": ADMIN_PASS},
|
if must_change:
|
||||||
headers={"Authorization": f"Bearer {token}"},
|
self.client.post(
|
||||||
)
|
"/api/v1/auth/change-password",
|
||||||
# Re-login for a clean token
|
json={"old_password": ADMIN_PASS, "new_password": ADMIN_PASS},
|
||||||
self.token = self._login_with_retry()
|
headers={"Authorization": f"Bearer {token}"},
|
||||||
|
)
|
||||||
|
token, _ = self._login_with_retry()
|
||||||
|
|
||||||
|
self.token = token
|
||||||
self.client.headers.update({"Authorization": f"Bearer {self.token}"})
|
self.client.headers.update({"Authorization": f"Bearer {self.token}"})
|
||||||
|
|
||||||
# --- Read-hot paths (high weight) ---
|
# --- Read-hot paths (high weight) ---
|
||||||
|
|||||||
Reference in New Issue
Block a user