603 lines
30 KiB
Python
Executable File
603 lines
30 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Unit and policy-matrix coverage for workspace descriptor documentation regions."""
|
|
|
|
import unittest
|
|
from dataclasses import dataclass
|
|
|
|
try:
|
|
from workspace_descriptor_doc_contract import (
|
|
ContractError,
|
|
_render_active_markdown,
|
|
check_document_text,
|
|
check_project_state_text,
|
|
scan_active_markdown,
|
|
)
|
|
except ModuleNotFoundError: # Support `python -m unittest scripts.test_...` from repo root.
|
|
from scripts.workspace_descriptor_doc_contract import (
|
|
ContractError,
|
|
_render_active_markdown,
|
|
check_document_text,
|
|
check_project_state_text,
|
|
scan_active_markdown,
|
|
)
|
|
|
|
WORKSPACE_START = "<!-- workspace-descriptor-contract:start -->"
|
|
WORKSPACE_END = "<!-- workspace-descriptor-contract:end -->"
|
|
NON_WORKSPACE_START = "<!-- non-workspace-migration:start -->"
|
|
NON_WORKSPACE_END = "<!-- non-workspace-migration:end -->"
|
|
|
|
@dataclass(frozen=True)
|
|
class PolicyCase:
|
|
name: str
|
|
expected: str
|
|
content: str
|
|
|
|
|
|
CASES = [
|
|
PolicyCase('m_schema_v1', 'R', 'Schema v1 descriptors return `migration_required`.'),
|
|
PolicyCase('m_schema_dash_v2', 'R', 'Schema-v2 descriptors report `migration_required`.'),
|
|
PolicyCase('m_schema_num1', 'R', 'Schema 1 descriptors remain `migration_required`.'),
|
|
PolicyCase('m_schema_word1', 'R', 'Schema version 1 descriptors remain `migration_required`.'),
|
|
PolicyCase('m_schema_under_colon', 'R', 'A descriptor with schema_version: 1 returns `migration_required`.'),
|
|
PolicyCase('m_schema_under_eq', 'R', 'A descriptor with schema_version=2 returns `migration_required`.'),
|
|
PolicyCase('m_schema_v1_and_v2', 'R', 'Schema v1 and v2 descriptors remain `migration_required`.'),
|
|
PolicyCase('m_bare_and', 'R', 'v1 and v2 descriptors remain `migration_required`.'),
|
|
PolicyCase('m_bare_or', 'R', 'v1 or v2 descriptors remain `migration_required`.'),
|
|
PolicyCase('m_bare_slash', 'R', 'v1/v2 descriptors remain `migration_required`.'),
|
|
PolicyCase('a_they', 'R', 'Schema v1 descriptors are rejected. They return `migration_required`.'),
|
|
PolicyCase('a_these_desc', 'R', 'Schema v1 descriptors are rejected. These descriptors return `migration_required`.'),
|
|
PolicyCase('a_those_desc', 'R', 'Schema v1 descriptors are rejected. Those descriptors return `migration_required`.'),
|
|
PolicyCase('a_such_desc', 'R', 'Schema v1 descriptors are rejected. Such descriptors return `migration_required`.'),
|
|
PolicyCase('a_their', 'R', 'Schema v1 descriptors are rejected. Their validation returns `migration_required`.'),
|
|
PolicyCase('a_the_desc', 'R', 'Schema v1 descriptors are rejected. The descriptors return `migration_required`.'),
|
|
PolicyCase('a_the_candidate', 'R', 'Schema v1 candidates are rejected. The candidate returns `migration_required`.'),
|
|
PolicyCase('a_still', 'R', 'Schema v1 descriptors are rejected, but still return `migration_required`.'),
|
|
PolicyCase('a_it', 'R', 'Schema v1 descriptor is rejected. It still returns `migration_required`.'),
|
|
PolicyCase('a_this_desc', 'R', 'Schema v1 descriptor is rejected. This descriptor still returns `migration_required`.'),
|
|
PolicyCase('a_that_desc', 'R', 'Schema v1 descriptor is rejected. That descriptor still returns `migration_required`.'),
|
|
PolicyCase('a_such_candidate', 'R', 'Schema v1 candidate is rejected. Such a candidate still returns `migration_required`.'),
|
|
PolicyCase('d_workspace', 'R', 'Workspace descriptors remain `migration_required`.'),
|
|
PolicyCase('d_possessive', 'R', "Workspace descriptors' status is `migration_required`."),
|
|
PolicyCase('d_validation', 'R', 'Workspace descriptor validation returns `migration_required`.'),
|
|
PolicyCase('d_legacy', 'R', 'Legacy descriptors remain `migration_required`.'),
|
|
PolicyCase('d_bare_descriptor', 'R', 'The descriptor remains `migration_required`.'),
|
|
PolicyCase('r_workspace', 'R', '`migration_required` is returned by workspace descriptors.'),
|
|
PolicyCase('r_workspace_status', 'R', '`migration_required` is the status for workspace descriptors.'),
|
|
PolicyCase('r_legacy', 'R', '`migration_required` applies to legacy descriptors.'),
|
|
PolicyCase('r_bare_descriptor', 'R', '`migration_required` is the status for the descriptor.'),
|
|
PolicyCase('u_standalone', 'A', 'The unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_after_period', 'A', 'Schema v1 descriptors are rejected. The unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_while', 'A', 'Schema v1 descriptors are rejected while the unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_and_the', 'A', 'Schema v1 descriptors are rejected and the unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_and_a', 'A', 'Schema v1 descriptors are rejected and a session database upgrade may report `migration_required`.'),
|
|
PolicyCase('u_and_this', 'A', 'Schema v1 descriptors are rejected and this unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_and_these', 'A', 'Schema v1 descriptors are rejected and these unrelated session database upgrades may report `migration_required`.'),
|
|
PolicyCase('u_and_our', 'A', 'Schema v1 descriptors are rejected and our unrelated session database may report `migration_required`.'),
|
|
PolicyCase('u_and_bare_subject', 'A', 'Schema v1 descriptors are rejected and session database upgrades may report `migration_required`.'),
|
|
PolicyCase('u_prev_then_these', 'A', 'Schema v1 descriptors are rejected. These unrelated session database upgrades report `migration_required`.'),
|
|
PolicyCase('u_api_versions', 'A', 'The unrelated session API v1 and v2 may report `migration_required`.'),
|
|
PolicyCase('u_db_schema_version', 'A', 'The unrelated session database schema version 1 may report `migration_required`.'),
|
|
PolicyCase('u_reverse_negative', 'A', '`migration_required` is not returned by workspace descriptors; it belongs to the session database.'),
|
|
PolicyCase('s_accept_although', 'A', 'Although schema v1 descriptors are rejected, the unrelated session database may report `migration_required`.'),
|
|
PolicyCase('s_accept_because', 'A', 'Schema v1 descriptors are rejected because the unrelated session database may report `migration_required`.'),
|
|
PolicyCase('s_accept_mentions_that', 'A', 'Schema v1 descriptor documentation mentions that the unrelated session database may report `migration_required`.'),
|
|
PolicyCase('s_accept_unrelated_descriptor', 'A', 'The unrelated session database descriptor reports `migration_required`.'),
|
|
PolicyCase('s_accept_unrelated_candidate', 'A', 'An unrelated candidate remains `migration_required`.'),
|
|
PolicyCase('s_reject_workspace_anaphor_they', 'R', 'Workspace descriptors are rejected. They return `migration_required`.'),
|
|
PolicyCase('s_reject_workspace_anaphor_it', 'R', 'A workspace descriptor is rejected. It returns `migration_required`.'),
|
|
PolicyCase('s_reject_descriptor_anaphor', 'R', 'The descriptor is rejected. It returns `migration_required`.'),
|
|
]
|
|
|
|
|
|
def workspace_region(extra=""):
|
|
body = (
|
|
"Schema v4 is the only accepted workspace descriptor. "
|
|
"Schema v1, v2, and v3 workspace descriptors are rejected before activation."
|
|
)
|
|
if extra:
|
|
body += "\n" + extra
|
|
return f"{WORKSPACE_START}\n{body}\n{WORKSPACE_END}"
|
|
|
|
|
|
def non_workspace_region(content):
|
|
return f"{NON_WORKSPACE_START}\n{content}\n{NON_WORKSPACE_END}"
|
|
|
|
|
|
def generic_fixture(*, workspace_extra="", current_extra=""):
|
|
return f"# Fixture\n\n{workspace_region(workspace_extra)}\n\n{current_extra}\n"
|
|
|
|
|
|
def project_fixture(*, workspace_extra="", current_extra="", historical_extra=""):
|
|
return (
|
|
"# Project state\n\n"
|
|
+ workspace_region(workspace_extra)
|
|
+ "\n\n"
|
|
+ current_extra
|
|
+ "\n\n# Historical archive\n"
|
|
+ "## Historical snapshots and archived reference notes\n\n"
|
|
+ historical_extra
|
|
+ "\n"
|
|
)
|
|
|
|
|
|
class PolicyMatrixTests(unittest.TestCase):
|
|
def test_52_case_matrix_across_both_entry_points(self):
|
|
self.assertEqual(len(CASES), 52)
|
|
for case in CASES:
|
|
if case.expected == "R":
|
|
generic = generic_fixture(workspace_extra=case.content)
|
|
project = project_fixture(workspace_extra=case.content)
|
|
else:
|
|
allowed = non_workspace_region(case.content)
|
|
generic = generic_fixture(current_extra=allowed)
|
|
project = project_fixture(current_extra=allowed)
|
|
for path, checker, text in (
|
|
("generic", check_document_text, generic),
|
|
("project", check_project_state_text, project),
|
|
):
|
|
with self.subTest(case=case.name, path=path, expected=case.expected):
|
|
if case.expected == "R":
|
|
with self.assertRaises(ContractError):
|
|
checker(text, f"{path}-{case.name}")
|
|
else:
|
|
checker(text, f"{path}-{case.name}")
|
|
|
|
|
|
class RegionStructureTests(unittest.TestCase):
|
|
def assert_invalid_both(self, generic, project=None):
|
|
with self.assertRaises(ContractError):
|
|
check_document_text(generic, "generic-invalid")
|
|
with self.assertRaises(ContractError):
|
|
check_project_state_text(project or project_fixture(current_extra=generic), "project-invalid")
|
|
|
|
def test_unmarked_unrelated_migration_required_is_rejected(self):
|
|
text = "The unrelated session database may report `migration_required`."
|
|
self.assert_invalid_both(generic_fixture(current_extra=text), project_fixture(current_extra=text))
|
|
|
|
def test_marked_unrelated_migration_required_is_accepted(self):
|
|
text = non_workspace_region("The session database may report `migration_required`.")
|
|
check_document_text(generic_fixture(current_extra=text), "generic-allowed")
|
|
check_project_state_text(project_fixture(current_extra=text), "project-allowed")
|
|
|
|
def test_missing_reversed_duplicate_and_nested_markers_are_rejected(self):
|
|
malformed = (
|
|
WORKSPACE_START,
|
|
WORKSPACE_END + "\n" + WORKSPACE_START,
|
|
workspace_region() + "\n" + workspace_region(),
|
|
WORKSPACE_START + "\n" + NON_WORKSPACE_START + "\n" + WORKSPACE_END + "\n" + NON_WORKSPACE_END,
|
|
NON_WORKSPACE_START + "\n" + NON_WORKSPACE_START + "\n" + NON_WORKSPACE_END + "\n" + NON_WORKSPACE_END,
|
|
NON_WORKSPACE_START + "\n" + workspace_region() + "\n" + NON_WORKSPACE_END,
|
|
workspace_region() + "\n" + NON_WORKSPACE_END,
|
|
)
|
|
for index, text in enumerate(malformed):
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(text)
|
|
|
|
def test_migration_required_inside_workspace_block_is_rejected(self):
|
|
text = generic_fixture(workspace_extra="Descriptors return `migration_required`.")
|
|
self.assert_invalid_both(text, project_fixture(workspace_extra="Descriptors return `migration_required`."))
|
|
|
|
def test_legacy_support_inside_workspace_block_is_rejected(self):
|
|
text = generic_fixture(workspace_extra="Schema v1 descriptors are operational and readable.")
|
|
self.assert_invalid_both(
|
|
text,
|
|
project_fixture(workspace_extra="Schema v1 descriptors are operational and readable."),
|
|
)
|
|
|
|
def test_migrate_legacy_is_forbidden_even_in_non_workspace_region(self):
|
|
allowed = non_workspace_region("Run `migrate-legacy` for the session database.")
|
|
self.assert_invalid_both(generic_fixture(current_extra=allowed), project_fixture(current_extra=allowed))
|
|
|
|
def test_project_requires_terminal_historical_h1(self):
|
|
valid = project_fixture(historical_extra="Schema v2 returned `migration_required` historically.")
|
|
check_project_state_text(valid, "project-valid-history")
|
|
for name, text in (
|
|
("missing", valid.replace("# Historical archive\n", "")),
|
|
(
|
|
"blank-physical-line",
|
|
valid.replace(
|
|
"# Historical archive\n## Historical",
|
|
"# Historical archive\n\n## Historical",
|
|
),
|
|
),
|
|
(
|
|
"prose-physical-line",
|
|
valid.replace(
|
|
"# Historical archive\n## Historical",
|
|
"# Historical archive\nArchived notes follow.\n## Historical",
|
|
),
|
|
),
|
|
(
|
|
"comment-physical-line",
|
|
valid.replace(
|
|
"# Historical archive\n## Historical",
|
|
"# Historical archive\n<!-- archived -->\n## Historical",
|
|
),
|
|
),
|
|
(
|
|
"intervening-h2",
|
|
valid.replace(
|
|
"# Historical archive\n## Historical",
|
|
"# Historical archive\n## Other archive\n## Historical",
|
|
),
|
|
),
|
|
(
|
|
"intervening-setext-h2",
|
|
valid.replace(
|
|
"# Historical archive\n## Historical",
|
|
"# Historical archive\nOther archive\n-------------\n## Historical",
|
|
),
|
|
),
|
|
(
|
|
"setext-replacement-h2",
|
|
valid.replace(
|
|
"## Historical snapshots",
|
|
"Historical snapshots\n--------------------",
|
|
),
|
|
),
|
|
("later-h1", valid + "\n# Returned live section\n"),
|
|
("duplicate-h1", valid + "\n# Historical archive\n"),
|
|
):
|
|
with self.subTest(name=name), self.assertRaises(ContractError):
|
|
check_project_state_text(text, f"project-{name}")
|
|
|
|
def test_markers_inside_fences_comments_or_code_do_not_count(self):
|
|
fake = workspace_region()
|
|
for name, wrapped in (
|
|
("backtick-fence", f"```markdown\n{fake}\n```"),
|
|
("tilde-fence", f"~~~~\n{fake}\n~~~~"),
|
|
("outer-comment", f"<!--\n{fake}\n-->"),
|
|
("indented-code", "\n".join(" " + line for line in fake.splitlines())),
|
|
):
|
|
generic = f"# Fixture\n\n{wrapped}\n"
|
|
project = (
|
|
f"# Project\n\n{wrapped}\n\n# Historical archive\n"
|
|
"## Historical snapshots\n"
|
|
)
|
|
with self.subTest(name=name):
|
|
self.assert_invalid_both(generic, project)
|
|
|
|
def test_extra_inactive_marker_literals_do_not_duplicate_active_region(self):
|
|
fake_region = f"{WORKSPACE_START}\ninactive example\n{WORKSPACE_END}"
|
|
extras = (
|
|
f"```\n{fake_region}\n```",
|
|
f" ~~~\n{fake_region}\n ~~~~",
|
|
"\n".join(" " + line for line in fake_region.splitlines()),
|
|
)
|
|
for index, extra in enumerate(extras):
|
|
with self.subTest(index=index):
|
|
check_document_text(generic_fixture(current_extra=extra), f"generic-extra-{index}")
|
|
check_project_state_text(
|
|
project_fixture(current_extra=extra), f"project-extra-{index}"
|
|
)
|
|
nested_comment = f"<!--\n{fake_region}\n-->"
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=nested_comment),
|
|
project_fixture(current_extra=nested_comment),
|
|
)
|
|
|
|
def test_inline_and_partially_indented_markers_are_misplaced(self):
|
|
for name, marker in (
|
|
("inline", "text <!-- workspace-descriptor-contract:start -->"),
|
|
("one-space", " <!-- workspace-descriptor-contract:start -->"),
|
|
("inline-allow", "text <!-- non-workspace-migration:start -->"),
|
|
("one-space-allow", " <!-- non-workspace-migration:start -->"),
|
|
):
|
|
generic = generic_fixture(current_extra=marker)
|
|
project = project_fixture(current_extra=marker)
|
|
with self.subTest(name=name):
|
|
self.assert_invalid_both(generic, project)
|
|
|
|
def test_inactive_allow_markers_do_not_authorize_but_extra_literals_are_ignored(self):
|
|
allow = non_workspace_region("migration_required")
|
|
fake_token_region = non_workspace_region("migration_required")
|
|
fake_empty_region = non_workspace_region("inactive example")
|
|
wrappers = (
|
|
(f"```\n{fake_token_region}\n```", f"```\n{fake_empty_region}\n```", True),
|
|
(f"<!--\n{fake_token_region}\n-->", f"<!--\n{fake_empty_region}\n-->", False),
|
|
(
|
|
"\n".join(" " + line for line in fake_token_region.splitlines()),
|
|
"\n".join(" " + line for line in fake_empty_region.splitlines()),
|
|
True,
|
|
),
|
|
)
|
|
for index, (fake_with_token, fake_without_token, extra_allowed) in enumerate(wrappers):
|
|
with self.subTest(index=index, mode="sole"):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=fake_with_token + "\nmigration_required"),
|
|
project_fixture(current_extra=fake_with_token + "\nmigration_required"),
|
|
)
|
|
extra_generic = generic_fixture(current_extra=fake_without_token + "\n" + allow)
|
|
extra_project = project_fixture(current_extra=fake_without_token + "\n" + allow)
|
|
with self.subTest(index=index, mode="extra"):
|
|
if extra_allowed:
|
|
check_document_text(extra_generic, f"generic-extra-allow-{index}")
|
|
check_project_state_text(extra_project, f"project-extra-allow-{index}")
|
|
else:
|
|
self.assert_invalid_both(extra_generic, extra_project)
|
|
|
|
def test_fence_closer_must_match_character_and_minimum_length(self):
|
|
fake = workspace_region()
|
|
for name, fenced in (
|
|
("short-backtick-close", f"````\n```\n{fake}"),
|
|
("wrong-character-close", f"~~~~\n```\n{fake}\n~~~~"),
|
|
):
|
|
project = (
|
|
f"# Project\n\n{fenced}\n\n# Historical archive\n"
|
|
"## Historical snapshots\n"
|
|
)
|
|
with self.subTest(name=name):
|
|
self.assert_invalid_both(f"# Fixture\n\n{fenced}\n", project)
|
|
safe_fake = f"{WORKSPACE_START}\ninactive example\n{WORKSPACE_END}"
|
|
valid_extra = f"````\n{safe_fake}\n`````"
|
|
check_document_text(generic_fixture(current_extra=valid_extra), "valid-long-close")
|
|
|
|
def test_noncanonical_contract_sentences_are_rejected(self):
|
|
variants = (
|
|
workspace_region().replace(
|
|
"Schema v4 is the only accepted workspace descriptor.",
|
|
"Only schema v4 workspace descriptors are accepted.",
|
|
),
|
|
workspace_region().replace(
|
|
"Schema v4 is the only accepted workspace descriptor.",
|
|
"Not Schema v4 is the only accepted workspace descriptor.",
|
|
),
|
|
workspace_region().replace(
|
|
"Schema v1, v2, and v3 workspace descriptors are rejected before activation.",
|
|
"Not Schema v1, v2, and v3 workspace descriptors are rejected before activation.",
|
|
),
|
|
)
|
|
for index, noncanonical in enumerate(variants):
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(
|
|
generic_fixture().replace(workspace_region(), noncanonical),
|
|
project_fixture().replace(workspace_region(), noncanonical),
|
|
)
|
|
|
|
def test_remaining_legacy_claim_and_deleted_transformer_are_rejected(self):
|
|
for claim in (
|
|
"The system supports schema v1 descriptors.",
|
|
"A v2 descriptor remains readable.",
|
|
"Legacy-descriptor activation is operational.",
|
|
):
|
|
with self.subTest(claim=claim):
|
|
self.assert_invalid_both(
|
|
generic_fixture(workspace_extra=claim),
|
|
project_fixture(workspace_extra=claim),
|
|
)
|
|
for deleted in (
|
|
"Run the deleted legacy descriptor transformer.",
|
|
"Invoke the descriptor legacy migrator.",
|
|
"Use the legacy transformer.",
|
|
):
|
|
with self.subTest(deleted=deleted):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=deleted),
|
|
project_fixture(current_extra=deleted),
|
|
)
|
|
|
|
def test_deleted_tools_are_rejected_after_active_markdown_rendering(self):
|
|
examples = (
|
|
"migrate-`**legacy**`",
|
|
"migrate-[legacy](https://example.invalid/tool)",
|
|
"migrate-[legacy][tool]\n\n[tool]: https://example.invalid/tool",
|
|
"legacy descriptor **transformer**",
|
|
"migrate-legacy",
|
|
"migrate-<span>legacy</span>",
|
|
r"migrate\-legacy",
|
|
"legacy descriptor trans<!-- hidden -->former",
|
|
"Run migrate-[legacy\n](https://example.invalid/tool)",
|
|
"Run migrate-[legacy\n][tool]\n\n[tool]: https://example.invalid/tool",
|
|
"migrate-``\nlegacy\n``",
|
|
"<!-- hidden --> migrate-legacy",
|
|
"<!-- hidden --> legacy descriptor transformer",
|
|
"migrate-`\nlegacy\n`",
|
|
"<!-- hidden\ncomment --> migrate-legacy",
|
|
"migrate-legacy <!-- hidden\ncomment --> safe suffix",
|
|
)
|
|
for index, example in enumerate(examples):
|
|
with self.subTest(index=index, example=example):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=example),
|
|
project_fixture(current_extra=example),
|
|
)
|
|
|
|
def test_deleted_tool_examples_in_inactive_markdown_are_allowed(self):
|
|
inactive_examples = (
|
|
"<!--\nmigrate-legacy\nlegacy descriptor transformer\n-->",
|
|
"<!-- migrate-legacy and legacy descriptor transformer -->",
|
|
"[safe label](https://example.invalid/migrate-legacy)",
|
|
"[safe label][tool]\n\n[tool]: https://example.invalid/migrate-legacy",
|
|
'<span data-example="migrate-legacy">safe text</span>',
|
|
"safe prefix <!-- migrate-legacy --> safe suffix",
|
|
"safe prefix <!--\nmigrate-legacy\nlegacy descriptor transformer\n--> safe suffix",
|
|
)
|
|
for index, example in enumerate(inactive_examples):
|
|
with self.subTest(index=index):
|
|
check_document_text(
|
|
generic_fixture(current_extra=example), f"generic-inactive-tool-{index}"
|
|
)
|
|
check_project_state_text(
|
|
project_fixture(current_extra=example), f"project-inactive-tool-{index}"
|
|
)
|
|
|
|
def test_deleted_tools_in_operator_visible_code_blocks_are_rejected(self):
|
|
examples = (
|
|
"```text\nmigrate-legacy\n```",
|
|
"~~~~\nlegacy descriptor transformer\n~~~~",
|
|
" migrate-legacy",
|
|
"\tlegacy descriptor transformer",
|
|
"```text\n[safe](https://example.invalid/migrate-legacy)\n```",
|
|
" <!-- migrate-legacy -->",
|
|
"```html\n<!-- migrate-legacy -->\n```",
|
|
"```html\n<!-- outer <!-- migrate-legacy --> -->\n```",
|
|
)
|
|
for index, example in enumerate(examples):
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=example),
|
|
project_fixture(current_extra=example),
|
|
)
|
|
|
|
def test_hidden_canonical_sentences_cannot_satisfy_workspace_contract(self):
|
|
canonical = workspace_region()[len(WORKSPACE_START) + 1 : -len(WORKSPACE_END) - 1]
|
|
hidden_bodies = (
|
|
f"<!--\n{canonical}\n-->",
|
|
f"```text\n{canonical}\n```",
|
|
"\n".join(" " + line for line in canonical.splitlines()),
|
|
f"`{canonical}`",
|
|
)
|
|
for index, hidden in enumerate(hidden_bodies):
|
|
hidden_region = f"{WORKSPACE_START}\n{hidden}\n{WORKSPACE_END}"
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(
|
|
generic_fixture().replace(workspace_region(), hidden_region),
|
|
project_fixture().replace(workspace_region(), hidden_region),
|
|
)
|
|
|
|
def test_current_legacy_schema_references_require_non_workspace_region(self):
|
|
claim = "Schema v1 workspace descriptors remain operational and readable."
|
|
visible_claims = (
|
|
claim,
|
|
f"```text\n{claim}\n```",
|
|
" " + claim,
|
|
)
|
|
for index, visible_claim in enumerate(visible_claims):
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=visible_claim),
|
|
project_fixture(current_extra=visible_claim),
|
|
)
|
|
hidden_claim = f"<!-- {claim} -->"
|
|
check_document_text(
|
|
generic_fixture(current_extra=hidden_claim), "generic-hidden-legacy"
|
|
)
|
|
check_project_state_text(
|
|
project_fixture(current_extra=hidden_claim), "project-hidden-legacy"
|
|
)
|
|
allowed = non_workspace_region(claim)
|
|
check_document_text(generic_fixture(current_extra=allowed), "generic-allowed-legacy")
|
|
check_project_state_text(
|
|
project_fixture(current_extra=allowed), "project-allowed-legacy"
|
|
)
|
|
|
|
def test_html_comments_fail_closed_when_unclosed_or_nested(self):
|
|
malformed = (
|
|
"<!-- unclosed comment",
|
|
"safe prefix <!-- unclosed comment",
|
|
"<!-- outer\n<!-- inner -->\n-->\nmigrate-legacy",
|
|
(
|
|
"<!-- outer\n<!-- inner -->\n-->\n"
|
|
"Schema v1 workspace descriptors remain operational and readable."
|
|
),
|
|
"<!-- outer\n<!-- inner -->\n-->\nsafe trailing text",
|
|
)
|
|
for index, text in enumerate(malformed):
|
|
with self.subTest(index=index):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=text),
|
|
project_fixture(current_extra=text),
|
|
)
|
|
|
|
visible_suffixes = (
|
|
"<!-- hidden --> migrate-legacy",
|
|
"<!-- hidden\n--> legacy descriptor transformer",
|
|
)
|
|
for index, text in enumerate(visible_suffixes):
|
|
with self.subTest(index=index, kind="visible-suffix"):
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=text),
|
|
project_fixture(current_extra=text),
|
|
)
|
|
|
|
pure_comments = (
|
|
"<!-- migrate-legacy -->",
|
|
"<!--\nlegacy descriptor transformer\n-->",
|
|
)
|
|
for index, text in enumerate(pure_comments):
|
|
with self.subTest(index=index, kind="pure-comment"):
|
|
check_document_text(
|
|
generic_fixture(current_extra=text), f"generic-pure-comment-{index}"
|
|
)
|
|
check_project_state_text(
|
|
project_fixture(current_extra=text), f"project-pure-comment-{index}"
|
|
)
|
|
|
|
def test_alternate_html_comment_closer_fails_closed_outside_code(self):
|
|
malformed = (
|
|
"<!-- hidden --!> migrate-legacy",
|
|
(
|
|
"<!-- hidden --!> "
|
|
"Schema v1 workspace descriptors remain operational and readable."
|
|
),
|
|
"<!-- hidden\n--!> migrate-legacy",
|
|
)
|
|
for index, text in enumerate(malformed):
|
|
generic = generic_fixture(current_extra=text)
|
|
project = project_fixture(current_extra=text)
|
|
with (
|
|
self.subTest(index=index, entry_point="generic"),
|
|
self.assertRaisesRegex(ContractError, "alternate HTML comment closer"),
|
|
):
|
|
check_document_text(generic, f"generic-alternate-closer-{index}")
|
|
with (
|
|
self.subTest(index=index, entry_point="project"),
|
|
self.assertRaisesRegex(ContractError, "alternate HTML comment closer"),
|
|
):
|
|
check_project_state_text(project, f"project-alternate-closer-{index}")
|
|
|
|
safe_code = (
|
|
"```html\n<!-- hidden --!> safe fenced example\n```\n"
|
|
" <!-- hidden --!> safe indented example"
|
|
)
|
|
check_document_text(generic_fixture(current_extra=safe_code), "generic-safe-code-closer")
|
|
check_project_state_text(
|
|
project_fixture(current_extra=safe_code), "project-safe-code-closer"
|
|
)
|
|
|
|
visible_code = (
|
|
"```html\n<!-- hidden --!> migrate-legacy\n```\n"
|
|
" <!-- hidden --!> Schema v1 workspace descriptors remain operational."
|
|
)
|
|
self.assert_invalid_both(
|
|
generic_fixture(current_extra=visible_code),
|
|
project_fixture(current_extra=visible_code),
|
|
)
|
|
|
|
def test_visible_comment_prefix_and_suffix_are_preserved_without_structural_reparse(self):
|
|
text = (
|
|
"safe prefix <!-- hidden --> safe suffix\n"
|
|
"before <!--\nhidden\n--> after\n"
|
|
"<!-- hidden --> # Reconstructed heading must not count\n"
|
|
)
|
|
scan = scan_active_markdown(text, "visible-comment-test")
|
|
self.assertEqual(
|
|
_render_active_markdown(scan),
|
|
"safe prefix safe suffix before after # Reconstructed heading must not count",
|
|
)
|
|
self.assertFalse(any(heading.text.startswith("Reconstructed") for heading in scan.headings))
|
|
|
|
def test_setext_h1_after_archive_is_rejected(self):
|
|
project = project_fixture(historical_extra="Returned live section\n=====================")
|
|
with self.assertRaises(ContractError):
|
|
check_project_state_text(project, "project-setext-live-tail")
|
|
|
|
def test_heading_literals_in_adversarial_fences_and_comments_are_inactive(self):
|
|
historical = (
|
|
"````\nFake live H1\n============\n```\n`````\n"
|
|
"<!--\nComment live H1\n===============\n-->"
|
|
)
|
|
check_project_state_text(
|
|
project_fixture(historical_extra=historical), "project-inactive-headings"
|
|
)
|
|
|
|
def test_historical_markers_cannot_satisfy_current_contract(self):
|
|
current_without = "# Project state\n\nNo current workspace contract.\n"
|
|
historical = "# Historical archive\n## Historical snapshots\n\n" + workspace_region()
|
|
with self.assertRaises(ContractError):
|
|
check_project_state_text(current_without + historical, "project-historical-marker")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main(verbosity=2)
|