e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
164 lines
6.4 KiB
Python
164 lines
6.4 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
|
|
|
|
"""Pin the auth-form input-count contract on the change-password page.
|
|
|
|
PR #5490 added a third "Current password" input, regressing first-boot UX to
|
|
three inputs; PR #5545 restores two by rendering it only when BOOTSTRAP is absent.
|
|
These tests inspect the source directly (no Studio/browser/network); runtime is
|
|
covered by tests/studio/playwright_chat_ui.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
from pathlib import Path
|
|
|
|
AUTH_FORM = (
|
|
Path(__file__).resolve().parents[2]
|
|
/ "studio/frontend/src/features/auth/components/auth-form.tsx"
|
|
)
|
|
|
|
CONDITIONAL_OPENER = "{!hasBootstrapPassword && ("
|
|
|
|
|
|
def _conditional_extent(src: str) -> tuple[int, int]:
|
|
"""(start, end) char offsets of the `{!hasBootstrapPassword && (...)}` JSX block."""
|
|
start = src.find(CONDITIONAL_OPENER)
|
|
assert start != -1, (
|
|
"the {!hasBootstrapPassword && (...)} JSX block that hides the "
|
|
"Current password input on first boot is missing -- PR #5545 has "
|
|
"been reverted or the conditional was inlined as a ternary"
|
|
)
|
|
depth = 1
|
|
i = start + len(CONDITIONAL_OPENER)
|
|
while i < len(src):
|
|
c = src[i]
|
|
if c == "(":
|
|
depth += 1
|
|
elif c == ")":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return start, i + 1
|
|
i += 1
|
|
raise AssertionError("unterminated !hasBootstrapPassword JSX block")
|
|
|
|
|
|
def test_hasbootstrappassword_constant_is_derived_from_bootstrap_window_value():
|
|
"""The guard must read from window.__UNSLOTH_BOOTSTRAP__, matching the backend's
|
|
bootstrap-injection contract in studio/backend/main.py::_inject_bootstrap."""
|
|
src = AUTH_FORM.read_text()
|
|
assert "const hasBootstrapPassword = Boolean(window.__UNSLOTH_BOOTSTRAP__?.password);" in src, (
|
|
"hasBootstrapPassword constant missing or its derivation drifted; "
|
|
"this is the gate that hides the Current password input on first boot"
|
|
)
|
|
|
|
|
|
def test_exactly_one_hasBootstrapPassword_conditional_exists():
|
|
"""Only one `!hasBootstrapPassword` JSX check is allowed; a second would split
|
|
rendering into branches and likely hide or duplicate the New / Confirm inputs."""
|
|
src = AUTH_FORM.read_text()
|
|
count = src.count("!hasBootstrapPassword")
|
|
assert count == 1, (
|
|
f"expected exactly one !hasBootstrapPassword usage, found {count}; "
|
|
"extra conditionals can hide or duplicate the always-on inputs"
|
|
)
|
|
|
|
|
|
def test_current_password_input_is_inside_the_hasBootstrapPassword_conditional():
|
|
"""`id="current-password"` must sit inside `{!hasBootstrapPassword && (...)}`,
|
|
else it renders on first boot too, regressing the pre-#5490 UX that PR #5545 restores."""
|
|
src = AUTH_FORM.read_text()
|
|
s, e = _conditional_extent(src)
|
|
idx = src.find('id="current-password"')
|
|
assert idx != -1, "the Current password input was removed entirely"
|
|
assert s < idx < e, (
|
|
"Current password input is rendered unconditionally; this is the "
|
|
"PR #5490 regression -- on first boot the bootstrap-derived "
|
|
"password is reused silently and only New + Confirm should render"
|
|
)
|
|
|
|
|
|
def test_new_password_input_is_outside_the_hasBootstrapPassword_conditional():
|
|
"""`id="new-password"` must sit outside `{!hasBootstrapPassword && (...)}`,
|
|
else it disappears on admin-forced resets, regressing PR #5490."""
|
|
src = AUTH_FORM.read_text()
|
|
s, e = _conditional_extent(src)
|
|
idx = src.find('id="new-password"')
|
|
assert idx != -1, "the New password input was removed entirely"
|
|
assert not (s < idx < e), (
|
|
"New password is wrapped in !hasBootstrapPassword; that would "
|
|
"hide the field on admin-forced resets, regressing PR #5490. "
|
|
"New password must always render in change-password mode."
|
|
)
|
|
|
|
|
|
def test_confirm_password_input_is_outside_the_hasBootstrapPassword_conditional():
|
|
"""Same as New password, for `id="confirm-password"`."""
|
|
src = AUTH_FORM.read_text()
|
|
s, e = _conditional_extent(src)
|
|
idx = src.find('id="confirm-password"')
|
|
assert idx != -1, "the Confirm password input was removed entirely"
|
|
assert not (s < idx < e), (
|
|
"Confirm password is wrapped in !hasBootstrapPassword; same "
|
|
"regression as New password -- it must always render in "
|
|
"change-password mode."
|
|
)
|
|
|
|
|
|
def test_change_password_jsx_declares_exactly_three_password_inputs():
|
|
"""The change-password JSX block (`{!isLoginMode && (...)}`) must declare exactly
|
|
current/new/confirm; a fourth would break the 2-input first-boot contract (the
|
|
conditional only hides Current)."""
|
|
src = AUTH_FORM.read_text()
|
|
start = src.find("{!isLoginMode && (")
|
|
assert start != -1, (
|
|
"the change-password JSX subtree marker {!isLoginMode && (...)} "
|
|
"is missing; the file's structure has drifted"
|
|
)
|
|
# Match the corresponding `)}` for {!isLoginMode && (...)}.
|
|
depth = 1
|
|
i = start + len("{!isLoginMode && (")
|
|
while i < len(src) and depth > 0:
|
|
c = src[i]
|
|
if c == "(":
|
|
depth += 1
|
|
elif c == ")":
|
|
depth -= 1
|
|
i += 1
|
|
subtree = src[start:i]
|
|
ids = sorted(re.findall(r'id="([a-z-]+-password)"', subtree))
|
|
assert ids == [
|
|
"confirm-password",
|
|
"current-password",
|
|
"new-password",
|
|
], (
|
|
"change-password JSX must declare exactly current-password, "
|
|
f"new-password, confirm-password; found {ids!r}. A fourth "
|
|
"password input would almost certainly break the 2-input "
|
|
"first-boot contract."
|
|
)
|
|
|
|
|
|
def test_login_jsx_declares_exactly_one_password_input():
|
|
"""The login JSX block (`isLoginMode && (...)`) must declare exactly one password
|
|
input (the bootstrap password pasted from the CLI); a second breaks the per-mode matrix."""
|
|
src = AUTH_FORM.read_text()
|
|
start = src.find("{isLoginMode && (")
|
|
assert start != -1, "the login JSX subtree marker is missing"
|
|
depth = 1
|
|
i = start + len("{isLoginMode && (")
|
|
while i < len(src) and depth > 0:
|
|
c = src[i]
|
|
if c == "(":
|
|
depth += 1
|
|
elif c == ")":
|
|
depth -= 1
|
|
i += 1
|
|
subtree = src[start:i]
|
|
ids = re.findall(r'id="([a-z-]+)"', subtree)
|
|
# Lock the count, not the spelling, so a rename does not falsely fail.
|
|
pw_ids = [x for x in ids if "password" in x]
|
|
assert len(pw_ids) == 1, (
|
|
f"login JSX must declare exactly one password-typed input; " f"found {pw_ids!r}"
|
|
)
|