Skip to content

No package symbol

no_package_symbol

Dual-criterion rule for integration / e2e tests.

Every test under tests/integration/ or tests/e2e/ must either

  • exercise a first-party Python symbol (criterion a), or
  • invoke a declared [project.scripts] entrypoint (criterion b).

A test in tests/e2e/ that exercises only Python symbols is mis-located (its real boundary is integration); a test that satisfies neither criterion is not exercising the package at all. Both shapes are reported with a verdict that drives a concrete fix-hint.

The rule mirrors :class:PrivateImportsRule structurally (rule_id / check / _scan_file / _build_check_result) but delegates the AST primitives to _shared so that pyramid_level and no_package_symbol consume the same first-party-symbol / in-package-script helpers.

NoPackageSymbolRule

Bases: ProjectRule

Flag integration / e2e tests that exercise neither symbol nor CLI.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/no_package_symbol.py
Python
@register_rule(category="test_quality")
class NoPackageSymbolRule(ProjectRule):
    """Flag integration / e2e tests that exercise neither symbol nor CLI."""

    @property
    def rule_id(self) -> str:
        """Stable identifier for this rule."""
        return "TEST_QUALITY_NO_PACKAGE_SYMBOL"

    def check(self, project_path: Path) -> CheckResult:
        """Scan integration/e2e tests for missing package exercise."""
        early = self.check_src(project_path)
        if early is not None:
            return early
        tests_dir = project_path / "tests"
        if not tests_dir.exists():
            return CheckResult(
                rule_id=self.rule_id,
                passed=True,
                message="no tests/ directory",
                severity=Severity.INFO,
                score=100,
            )
        ctx = _ScanContext(
            project_path=project_path,
            pkg_prefixes=get_pkg_prefixes(project_path),
            project_scripts=load_project_scripts(project_path),
        )
        findings: list[_Finding] = []
        for test_file, tree in iter_test_files(project_path):
            if tree is None:
                continue
            level = current_level_from_path(test_file, tests_dir)
            if level not in {"integration", "e2e"}:
                continue
            findings.extend(self._scan_file(test_file, tree, level, ctx))
        return self._build_check_result(findings)

    def _scan_file(
        self,
        test_file: Path,
        tree: ast.Module,
        level: str,
        ctx: _ScanContext,
    ) -> list[_Finding]:
        """Report one finding per file when NO test exercises the package.

        The rule's intent is per-file: a test module that nowhere touches a
        first-party symbol or in-package CLI is the offender. Mixing one
        fixture-validation test with one symbol-exercising test is fine.
        """
        if file_has_module_marker(tree, _MARKER_NAME):
            return []
        aggregate = _aggregate_file_verdicts(tree, level, ctx)
        if aggregate is None:
            return []
        try:
            rel = test_file.relative_to(ctx.project_path).as_posix()
        except ValueError:
            rel = str(test_file)
        return [_finding_for_aggregate(rel, aggregate, level)]

    def _build_check_result(self, findings: list[_Finding]) -> CheckResult:
        n = len(findings)
        score = max(0, 100 - n * _SCORE_PENALTY)
        passed = n == 0
        if passed:
            message = "every integration/e2e test exercises the package"
        else:
            message = f"{n} integration/e2e test(s) exercise no package symbol or CLI"
        return CheckResult(
            rule_id=self.rule_id,
            passed=passed,
            message=message,
            severity=Severity.WARNING,
            score=score,
            details={"findings": [f.as_dict() for f in findings]},
            fix_hint=_pick_fix_hint(findings),
        )
rule_id property

Stable identifier for this rule.

check(project_path)

Scan integration/e2e tests for missing package exercise.

Source code in packages/axm-audit/src/axm_audit/core/rules/test_quality/no_package_symbol.py
Python
def check(self, project_path: Path) -> CheckResult:
    """Scan integration/e2e tests for missing package exercise."""
    early = self.check_src(project_path)
    if early is not None:
        return early
    tests_dir = project_path / "tests"
    if not tests_dir.exists():
        return CheckResult(
            rule_id=self.rule_id,
            passed=True,
            message="no tests/ directory",
            severity=Severity.INFO,
            score=100,
        )
    ctx = _ScanContext(
        project_path=project_path,
        pkg_prefixes=get_pkg_prefixes(project_path),
        project_scripts=load_project_scripts(project_path),
    )
    findings: list[_Finding] = []
    for test_file, tree in iter_test_files(project_path):
        if tree is None:
            continue
        level = current_level_from_path(test_file, tests_dir)
        if level not in {"integration", "e2e"}:
            continue
        findings.extend(self._scan_file(test_file, tree, level, ctx))
    return self._build_check_result(findings)