feat: add per-user avatar seed regeneration with irreversible random avatar replacement
Implement a new `avatar_seed` column on the users table that overrides the username-based seed for Multiavatar generation. Introduce a null-safe `avatar_seed(user)` choke point in `avatar.py` that resolves `user.get("avatar_seed") or user.get("username")`, registered as a Jinja global so every render site (`_avatar_link.html`, `avatar_url(...)` calls, SEO `og_image`, issues ad-hoc dicts, devRant payload/PNG) propagates a regenerated seed. Add `POST /profile/{username}/regenerate-avatar` endpoint (owner-or-admin only) that writes a fresh `generate_uid()` to `avatar_seed`, invalidates the target's user cache, and audits `profile.avatar.regenerate`. The previous seed is overwritten and never stored, making regeneration irreversible. Document the feature in `AGENTS.md` and `README.md`, add the API endpoint to `docs_api.py`, and include the `regenerate_avatar` Devii tool in `CONFIRM_REQUIRED`.
2026-06-28 00:31:34 +02:00
|
|
|
# retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from pydantic import ValidationError
|
|
|
|
|
|
|
|
|
|
from devplacepy.models import GameLegacyForm
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_game_legacy_form_accepts_valid_key():
|
|
|
|
|
assert GameLegacyForm(key="multiplier").key == "multiplier"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_game_legacy_form_rejects_empty_key():
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
GameLegacyForm(key="")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_game_legacy_form_rejects_overlong_key():
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
GameLegacyForm(key="x" * 41)
|
2026-07-06 05:57:47 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_isslop_run_form_accepts_git_and_http_sources():
|
|
|
|
|
from devplacepy.models import IsslopRunForm
|
|
|
|
|
|
|
|
|
|
assert IsslopRunForm(url="https://github.com/owner/repo").url == "https://github.com/owner/repo"
|
|
|
|
|
assert IsslopRunForm(url="git@github.com:owner/repo.git").url == "git@github.com:owner/repo.git"
|
|
|
|
|
assert IsslopRunForm(url=" https://example.com ").url == "https://example.com"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_isslop_run_form_rejects_other_schemes():
|
|
|
|
|
from devplacepy.models import IsslopRunForm
|
|
|
|
|
|
|
|
|
|
for bad in ("ftp://example.com/file", "javascript:alert(1)//aaa", "file:///etc/passwd"):
|
|
|
|
|
with pytest.raises(ValidationError):
|
|
|
|
|
IsslopRunForm(url=bad)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_isslop_run_form_normalizes_typos_and_bare_domains():
|
|
|
|
|
from devplacepy.models import IsslopRunForm
|
|
|
|
|
|
|
|
|
|
assert (
|
|
|
|
|
IsslopRunForm(url="https:/retoor.molodetz.nl/retoor/devplacepy").url
|
|
|
|
|
== "https://retoor.molodetz.nl/retoor/devplacepy"
|
|
|
|
|
)
|
|
|
|
|
assert IsslopRunForm(url="example.com/owner/repo").url == "https://example.com/owner/repo"
|
|
|
|
|
assert IsslopRunForm(url="http:/x.dev/a").url == "http://x.dev/a"
|