Files
DECNET/tests/realism/test_personas.py
anti f2b3393669 chore: relicense to AGPL-3.0-or-later and add SPDX headers
Replaces LICENSE (GPLv3 -> AGPLv3) and prepends
`SPDX-License-Identifier: AGPL-3.0-or-later` to every source file
across decnet/, decnet_web/, tests/, scripts/, and tools/.

Rationale: closes the GPLv3 ASP loophole so any party operating a
modified DECNET as a network service must offer their modified
source. Personal copyright (Samuel Paschuan) + inbound=outbound
contributions make a future unilateral relicense infeasible.

- LICENSE: full AGPL-3.0 text (gnu.org/licenses/agpl-3.0.txt)
- COPYRIGHT: project copyright notice
- tools/add_spdx_headers.py: idempotent header injector
  (shebang- and PEP 263-aware)

Touches 1565 source files (.py, .ts, .tsx, .js, .jsx, .css, .sh).
No behavior change; comments only.
2026-05-22 21:04:16 -04:00

151 lines
4.6 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-or-later
"""Persona schema parsing + active-hours window tests."""
from __future__ import annotations
import json
from datetime import datetime
from decnet.realism.personas import (
EmailPersona,
in_active_hours,
login_for,
parse_personas,
)
def _persona(**over) -> dict:
base = {
"name": "John Smith",
"email": "john@corp.com",
"role": "COO",
"tone": "formal",
"mannerisms": ["uses 'Best regards'"],
}
base.update(over)
return base
def test_parse_empty_inputs():
assert parse_personas(None) == []
assert parse_personas("") == []
assert parse_personas([]) == []
def test_parse_invalid_json_returns_empty_no_raise():
assert parse_personas("{not json") == []
def test_parse_invalid_top_level_shape_returns_empty():
assert parse_personas('{"not": "a list"}') == []
def test_parse_drops_invalid_entry_keeps_valid():
raw = json.dumps([
_persona(),
{"name": "broken", "email": "not-an-email"},
_persona(name="Sarah", email="sarah@corp.com"),
])
parsed = parse_personas(raw)
assert len(parsed) == 2
assert {p.name for p in parsed} == {"John Smith", "Sarah"}
def test_parse_resolves_language_default_when_unset():
raw = json.dumps([_persona()])
parsed = parse_personas(raw, language_default="es")
assert parsed[0].language == "es"
def test_parse_persona_language_overrides_default():
raw = json.dumps([_persona(language="pt")])
parsed = parse_personas(raw, language_default="es")
assert parsed[0].language == "pt"
def test_parse_accepts_python_list_directly():
parsed = parse_personas([_persona()])
assert len(parsed) == 1
def test_uses_llms_heavily_default_false():
parsed = parse_personas([_persona()])
assert parsed[0].uses_llms_heavily is False
def test_uses_llms_heavily_can_be_set():
parsed = parse_personas([_persona(uses_llms_heavily=True)])
assert parsed[0].uses_llms_heavily is True
def _dt(h: int, m: int = 0) -> datetime:
return datetime(2024, 1, 15, h, m)
def test_active_hours_normal_window():
p = EmailPersona(**_persona(active_hours="09:00-18:00"))
assert in_active_hours(p, _dt(12)) is True
assert in_active_hours(p, _dt(8)) is False
assert in_active_hours(p, _dt(18)) is False
assert in_active_hours(p, _dt(9)) is True
def test_active_hours_wraparound_window():
p = EmailPersona(**_persona(active_hours="22:00-06:00"))
assert in_active_hours(p, _dt(23)) is True
assert in_active_hours(p, _dt(0)) is True
assert in_active_hours(p, _dt(5)) is True
assert in_active_hours(p, _dt(7)) is False
def test_active_hours_malformed_treats_as_always_on():
p = EmailPersona(**_persona(active_hours="garbage"))
assert in_active_hours(p, _dt(0)) is True
assert in_active_hours(p, _dt(23)) is True
def test_active_hours_equal_window_treated_as_always_on():
p = EmailPersona(**_persona(active_hours="10:00-10:00"))
assert in_active_hours(p, _dt(5)) is True
def test_active_hours_minute_precision_start_boundary():
p = EmailPersona(**_persona(active_hours="09:30-17:45"))
assert in_active_hours(p, _dt(9, 15)) is False
assert in_active_hours(p, _dt(9, 29)) is False
assert in_active_hours(p, _dt(9, 30)) is True
assert in_active_hours(p, _dt(17, 44)) is True
assert in_active_hours(p, _dt(17, 45)) is False
def test_active_hours_minute_precision_wraparound():
p = EmailPersona(**_persona(active_hours="22:30-06:15"))
assert in_active_hours(p, _dt(22, 29)) is False
assert in_active_hours(p, _dt(22, 30)) is True
assert in_active_hours(p, _dt(6, 14)) is True
assert in_active_hours(p, _dt(6, 15)) is False
def test_login_for_normalises_display_name():
assert login_for("John Smith") == "johnsmith"
assert login_for("alice") == "alice"
def test_login_for_falls_back_to_user_on_punctuation():
# The realism namer and canary cultivator both rely on this so the
# decky filesystem doesn't end up with an unexpected username.
assert login_for("Mr. Robot") == "user"
assert login_for("") == "user"
assert login_for("Renée") == "user" # non-ASCII falls back
def test_login_for_shared_by_naming_and_cultivator():
"""Single source of truth: realism naming and canary cultivator
must agree on the persona→login mapping."""
from decnet.canary import cultivator
from decnet.realism import naming
persona = "John Smith"
expected = login_for(persona)
assert naming._home(persona) == f"/home/{expected}"
# cultivator imports login_for; not duplicated.
assert cultivator.login_for is login_for