Skip to content

test: exterminate test-law violations; fix aliased/set model commands - #200

Merged
marlon-costa-dc merged 6 commits into
0.12.0-devfrom
fix/test-law-extermination
Sep 26, 2026
Merged

marlon-costa-dc merged 6 commits into
0.12.0-devfrom
fix/test-law-extermination

Conversation

@marlon-costa-dc

@marlon-costa-dc marlon-costa-dc commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Exterminates test-law violations in flext-cli (operator orders 2026-09-26): tests verify behaviour through public facades, never how code is built. Coverage floors are released; this PR does not add coverage for its own sake.

Test-law counts (inventory rules from flext-infra PR #875, same sg scan invocation as test-law-inventory.md)

rule before after
ban-test-tautology 7 0
ban-test-fakes 2 0
ban-test-private-access 1 0
ban-test-doubles / ban-private-import / ban-getattr-private-probe 0 0
total 10 0

The sweep went past the inventory and also removed module-shape, signature-introspection, internal-name and owned-value assertions.

Deleted / rewritten tests

  • test_output_cov: _FakeSummaryStats / _FakeProjectFailureInfo replaced by frozen m.Tests.SummaryStats / m.Tests.ProjectFailureInfo models.
  • test_constants: deleted tests that restated owned constants and defaults as literals or checked how authority tuples are derived. Kept the invariants: map coverage, template interpolation, glyph distinctness, and the classifiers.
  • test_base: deleted the isinstance-of-just-built tautology and the tests of test scaffolding.
  • cli_service part_03: shared flags are now compared with their state before the invocation, not with literal defaults.
  • Prompts: deleted the subclass doubles that overwrote _input_reader, _password_reader and _test_env_override and overrode _log. Tests now build the real FlextCliPrompts with scripted input ports. Deleted the tests that asserted logging side effects. Hardcoded error strings now use the c.Cli constants, and password tests take their secrets from the fixture.
  • test_options_cov: inspect.signature probing is replaced by registering the command in a real app and calling cli.invoke_app. The rewritten tests cover alias, custom declarations, bool toggle, every annotation, a missing required option, the settings default and the model defaults.
  • test_protocols: deleted. It checked runtime_checkable, namespace identity and x is x with stub classes, and the product never checks those protocols at runtime.
  • Public contracts / model_commands / version / streamed process: removed signature probing, callable() and determinism tautologies, arbitrary length bounds, and duplicate semver tests (including tests of the flext-core regex). Thread-leak checks now take a live-thread snapshot instead of matching the internal pump thread name.

Product fixes (found by the behavioural rewrites; the signature tests passed while these were broken)

  • services/_cli_parts/flextclicli_part_01.py (_ModelCommand.__call__): Typer passes options under the field name, so an aliased field failed validation and the command exited non-zero. It now calls model_validate(kwargs, by_name=True).
  • _utilities/_options_parts/flextcliutilitiesoptions_part_01.py (resolve_typer_annotation): set / frozenset fields resolved to a bare set / frozenset, and Typer rejects those at invocation with RuntimeError: Type not yet supported. They now resolve to a repeated list[...] option, which the request model validates into a set.
  • services/_prompts_support.py: input_reader / password_reader are now public, dump-excluded DI ports (stdin and getpass by default). The test-only _test_env_override hook is removed.

Validation (lane worktree)

  • make gen ×2: exit 0, clean tree both times (fixed point).
  • make check: exit 0, Total: 1 Success: 1 Failed: 0.
  • make test: exit 0. make test-full: exit 0, 1195 passed. The baseline before this change was 1265 passed; the difference is the tests deleted here.

Known residual (not changed here)

  • A mapping-typed model field still resolves to dict, and Typer rejects it at invocation (Type not yet supported). It fails loudly, not silently, but it means the mapping branch of u.Cli.field_default never produces a command that can be invoked. This needs a product decision.

🤖 Generated with Claude Code


Summary by cubic

Rewrites the flext-cli tests to verify behavior through public facades instead of implementation details, clearing the 10 test-law violations plus signature-probing, tautology, and constant-restating assertions. The behavioral rewrites exposed two product bugs the old signature-based tests hid; fixing them makes aliased and set-typed model commands invocable.

Bug Fixes

  • _ModelCommand.__call__ now calls model_validate(kwargs, by_name=True) so aliased fields pass validation (Typer passes options under the field name).
  • set / frozenset fields now resolve to a repeated list option, which the request model validates into a set; Typer previously rejected the bare set type at invocation.

Refactors

  • input_reader / password_reader became public, dump-excluded DI ports on FlextCliPromptsSupport; the test-only _test_env_override hook and the prompt subclass doubles were removed, and prompt tests now inject scripted readers into the real service.
  • Deleted test_protocols.py and assertions that restated constants, introspected signatures, or matched the internal pump thread name; streamed-process tests now rely on live-thread snapshots.

Known residual: a mapping-typed model field still resolves to dict and Typer rejects it loudly at invocation, so the mapping branch of u.Cli.field_default cannot produce an invocable command; a product decision is needed.

Written for commit cf528cf. Summary will update on new commits.

Review in cubic

Marlon Costa and others added 6 commits September 26, 2026 05:30
…dels

Drop the ad-hoc _FakeSummaryStats/_FakeProjectFailureInfo classes
(ban-test-fakes) and feed u.Cli.summary/project_failure real frozen
m.Tests models that satisfy p.Cli.SummaryStats/ProjectFailureInfo.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- test_constants: delete tests that restate owned constants/defaults as
  literals or assert how authority tuples are derived; keep invariants
  (map coverage, glyph distinctness, template interpolation, classifiers).
- test_base: delete isinstance-of-just-built tautology and tests of the
  test service scaffolding.
- cli_service part_03: assert shared flags are preserved relative to the
  pre-invocation state instead of restating settings defaults.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…doubles

The prompt tests subclassed FlextCliPrompts and overwrote private state
(_input_reader, _password_reader, _test_env_override) and overrode _log
(ban-test-private-access), coupling them to internals.

- FlextCliPromptsSupport: input_reader/password_reader become public,
  dump-excluded DI ports (stdin/getpass by default); the test-only
  _test_env_override hook is removed.
- conftest: one make_prompts fixture builds the real FlextCliPrompts with
  scripted ports; capture/failing-log doubles and their protocols deleted.
- Deleted tests that asserted logging side effects or pinned test-env
  logging (behaviour outside the public result contract).
- Hardcoded error strings replaced by the owning c.Cli constants.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The model_command tests read the generated signature via
inspect.signature (construction, not behaviour). Rewritten to register
the command in a real app and invoke it with arguments, they exposed two
product defects the signature tests hid:

- aliased fields: Typer passes options under the field name, so
  model_validate rejected them; validate with by_name=True.
- set/frozenset fields resolved to bare set/frozenset, which Typer
  rejects at invocation (Type not yet supported); they now resolve to a
  repeated list option the request model validates into a set.

Mapping-typed test fields that no Typer command can accept, their
constants, and the FrameworkOption test protocol are deleted.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- test_protocols: deleted; it asserted how protocols are declared
  (runtime_checkable, namespace identity, x is x) using stub classes, and
  none of those protocols is checked at runtime by the product.
- public contracts: drop inspect.signature probing, the isinstance of the
  returned component model, and the restated 'available' literal.
- model_commands: drop callable(cmd) tautology (invocation is covered).
- streamed process: prove no leaked threads by live-thread snapshot
  instead of matching the internal pump thread name; drop the isinstance
  of a just-built deadline model.
- version/constants: drop arbitrary length bounds, determinism
  tautology, duplicated semver checks, and flext-core regex tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- prompt password tests take their secrets from scripted_password_pair
  instead of literals (ruff S106).
- annotation invocation cases expect the directly constructed model, so
  set/frozenset cases type-check (pyrefly bad-assignment).
- make fmt output adopted (including _json/_navigate.py wrapping).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@marlon-costa-dc
marlon-costa-dc merged commit 476e340 into 0.12.0-dev Sep 26, 2026
7 of 9 checks passed
@marlon-costa-dc
marlon-costa-dc deleted the fix/test-law-extermination branch September 26, 2026 08:50
marlon-costa-dc added a commit that referenced this pull request Sep 26, 2026
…options; fail-loud prompt printing (#201)

## Summary

Resolves the known residual from #200: a model field typed as a mapping
became a `dict` Typer option, which Typer rejects at invocation, so the
mapping branch of `u.Cli.field_default` never produced a usable command.

Decision (Pydantic law: owned payloads are Pydantic two-way; JSON enters
through Pydantic validation): a field without a native CLI form — a
mapping, a nested model, or a collection/union that carries one — is
exposed as **one string option carrying JSON**, validated by Pydantic
into the field's declared type.

## Design

- `u.Cli.is_json_option(annotation)` (new, `_options_parts/…part_01`) is
the single decision owner. It strips aliases and `Annotated`
(`u.Cli.unwrap_annotation`, shared with the resolver), resolves
subscripted type-alias origins (`t.MappingKV[...]` → `Mapping`), and
flags `Mapping` subclasses, `m.BaseModel` subclasses, and
generics/unions carrying them.
- `u.Cli.resolve_typer_annotation` returns `str` for those fields (the
old `dict` branch is gone).
- `u.Cli.framework_build_parameter(..., json_annotation=...)` sets the
Typer `parser` and the `JSON` metavar. The parser builds
`TypeAdapter(<declared type>)` **only at parse time** and runs
`validate_json`; a Pydantic error becomes `typer.BadParameter` chained
`from` the `ValidationError` (Click's parser hook otherwise drops a
`ValueError`'s text). `cli.execute_app` returns it as
`e.fail_validation` with the Pydantic cause; `cli.invoke_app` exits 2
before the handler runs.
- `u.Cli.field_default` renders a JSON option's default (settings first,
then model default/factory, including nested model instances) as JSON
text through `u.to_json`; the old per-entry mapping normalization and
the now-unused `t.Cli.MutableDefaultMapping` alias are removed.
- Help appends `c.Cli.CLI_JSON_OPTION_HELP` to the field description and
shows the `JSON` metavar and the JSON default.

## Fail-loud fix

`FlextCliPromptsSupport._print_message` caught a logger failure and
returned a formatted failed Result. The catch is removed so the failure
propagates with its cause; the per-caller error templates that only fed
that branch are removed from `prompts.py`.

## Tests (behavioural, public surface, no mocks)

New `tests/unit/test_model_command_json_options.py` registers
`cli.model_command` in a real app:
- valid JSON mapping reaches the model; nested model and `list[model]`
options reach the model;
- omitted JSON options deliver model defaults, or settings values when a
settings model is given;
- malformed JSON (`json_invalid`) and wrong types (`int_parsing`) fail:
`invoke_app` not succeeded and handler not called; `execute_app` is a
failed Result carrying the Pydantic error type;
- `--help` renders the JSON options with the `JSON` metavar.

`test_options_public_cov` updated to the new contract (mapping resolves
to `str`; mapping default is JSON text that decodes to the settings
value).

## Validation (lane worktree
`/home/marlonsc/flext-work/v8-cli-mapping/flext-cli`)

- `make gen` ×2: exit 0 both, identical diff (fixed point).
- `make check`: exit 0, `Total: 1 Success: 1 Failed: 0` (lint, pyrefly,
mypy, pyright, silent-failure, security, … OK;
duplication/codemod/boundary/namespace/runtime-census are SUSPENDED by
the check owner under flext-itpd1.3).
- `make test-full`: exit 0, incremental `78 passed`, full `1202 passed`,
0 failures.

## Residuals (not changed here)

- `u.Cli.build_model_command` (`_utilities/model_commands.py` `Builder`)
is a second model-command path that passes raw field annotations to
Typer, so it still cannot handle mapping fields; it duplicates
`cli.model_command`.
- `u.Cli.field_default` still catches `c.EXC_VALIDATION_TYPE_VALUE` and
maps a non-CLI default to `None`.
- Other `CLI_SAFE_EXCEPTIONS` catches in `_prompts_support.py`
(`_run_or_fail`) were not flagged and remain.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes model commands with mapping fields — previously a `dict` Typer
option that Typer rejects at invocation — and makes prompt printing fail
loud.

A model field without a native CLI form (a mapping, a nested model, or a
collection/union carrying one) now becomes one string option with the
`JSON` metavar. Pydantic validates the text into the field's declared
type at parse time.

- Malformed JSON or a schema mismatch is a usage error, and
`execute_app` returns it with the Pydantic cause.
- Defaults (settings first, then the model) render as JSON text; help
shows the format marker and never builds an adapter.
- `_print_message` no longer swallows logger failures — they propagate
with their cause, and the per-caller error templates are removed.

<sup>Written for commit dbcdb0e.
Summary will update on new commits.</sup>

<a
href="https://cubic.dev/pr/flext-sh/flext-cli/pull/201?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>

<!-- End of auto-generated description by cubic. -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant