Node test-quality rules — the AXM-specific test-hygiene invariants for TS.
These have no off-the-shelf node tool; they port the intent of the Python
test_quality rules to TypeScript, adapted to node conventions:
- mirror — every source module has a test. In node the de-facto idiom is a
colocated
foo.test.ts next to foo.ts (not a separate tests/ tree),
so the node mirror is sibling-based.
- pyramid level — colocated
*.test.ts are unit; tests under tests/ or
e2e/ / *.spec.ts with Playwright are higher levels. We flag colocated
unit tests that do real I/O (a soft signal they belong in integration/e2e).
- tautology — weak assertions (
expect(true)…, expect(x).toBe(x)).
- duplicate — identical test bodies.
The first two are file-system + light source signals; the last two read the TS
AST through axm-ast (analyze_package), which now supports .ts.
NodeTestDuplicateRule
Bases: ProjectRule
Flag duplicate test bodies (identical it/test blocks).
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| @register_rule("test_quality", framework=Framework.NODE)
class NodeTestDuplicateRule(ProjectRule):
"""Flag duplicate test bodies (identical ``it``/``test`` blocks)."""
@property
def rule_id(self) -> str:
"""Unique identifier (shared with the Python duplicate-tests rule)."""
return "TEST_QUALITY_DUPLICATE_TESTS"
def check(self, project_path: Path) -> CheckResult:
"""Count test cases whose normalized body duplicates an earlier one."""
if not (project_path / "package.json").is_file():
return _no_package_json(project_path, self.rule_id)
seen: set[str] = set()
duplicates = 0
for test in _all_test_files(project_path):
text = test.read_text(encoding="utf-8", errors="replace")
for match in _TEST_CASE.finditer(text):
body = _normalize_body(match.group("body"))
if len(body) < _MIN_BODY_LEN:
continue
if body in seen:
duplicates += 1
else:
seen.add(body)
# Duplicate test bodies are zero-tolerance: any duplicate fails.
score = max(0, 100 - duplicates * 10)
passed = duplicates == 0
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"No duplicate test bodies"
if not duplicates
else f"{duplicates} duplicate test body(ies)"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"duplicate_count": duplicates},
fix_hint="Merge or parametrize duplicate tests" if duplicates else None,
)
|
rule_id
property
Unique identifier (shared with the Python duplicate-tests rule).
check(project_path)
Count test cases whose normalized body duplicates an earlier one.
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| def check(self, project_path: Path) -> CheckResult:
"""Count test cases whose normalized body duplicates an earlier one."""
if not (project_path / "package.json").is_file():
return _no_package_json(project_path, self.rule_id)
seen: set[str] = set()
duplicates = 0
for test in _all_test_files(project_path):
text = test.read_text(encoding="utf-8", errors="replace")
for match in _TEST_CASE.finditer(text):
body = _normalize_body(match.group("body"))
if len(body) < _MIN_BODY_LEN:
continue
if body in seen:
duplicates += 1
else:
seen.add(body)
# Duplicate test bodies are zero-tolerance: any duplicate fails.
score = max(0, 100 - duplicates * 10)
passed = duplicates == 0
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"No duplicate test bodies"
if not duplicates
else f"{duplicates} duplicate test body(ies)"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"duplicate_count": duplicates},
fix_hint="Merge or parametrize duplicate tests" if duplicates else None,
)
|
NodeTestMirrorRule
Bases: ProjectRule
Every source module has a colocated *.test.ts (node mirror idiom).
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| @register_rule("practices", framework=Framework.NODE)
class NodeTestMirrorRule(ProjectRule):
"""Every source module has a colocated ``*.test.ts`` (node mirror idiom)."""
@property
def rule_id(self) -> str:
"""Unique identifier (shared with the Python mirror rule)."""
return "PRACTICE_TEST_MIRROR"
def check(self, project_path: Path) -> CheckResult:
"""Flag source modules with no sibling test file."""
src = _src_dir(project_path)
if src is None:
return _no_package_json(project_path, self.rule_id)
missing: list[str] = []
for source in _iter_source_files(src):
if source.stem in _MIRROR_EXEMPT_STEMS:
continue
if not self._has_sibling_test(source):
missing.append(source.relative_to(src).as_posix())
score = max(0, 100 - len(missing) * 15)
passed = score >= PASS_THRESHOLD
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"All source modules have colocated tests"
if not missing
else f"{len(missing)} module(s) without a colocated test"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"missing": missing[:20]},
fix_hint=(
f"Add colocated tests: {', '.join(missing[:5])}" if missing else None
),
)
@staticmethod
def _has_sibling_test(source: Path) -> bool:
"""Return True if a ``<stem>.test.ts``/``.spec.ts`` sits beside *source*."""
stem = source.stem
for suffix in _TEST_SUFFIXES:
if (source.parent / f"{stem}{suffix}").is_file():
return True
return False
|
rule_id
property
Unique identifier (shared with the Python mirror rule).
check(project_path)
Flag source modules with no sibling test file.
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| def check(self, project_path: Path) -> CheckResult:
"""Flag source modules with no sibling test file."""
src = _src_dir(project_path)
if src is None:
return _no_package_json(project_path, self.rule_id)
missing: list[str] = []
for source in _iter_source_files(src):
if source.stem in _MIRROR_EXEMPT_STEMS:
continue
if not self._has_sibling_test(source):
missing.append(source.relative_to(src).as_posix())
score = max(0, 100 - len(missing) * 15)
passed = score >= PASS_THRESHOLD
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"All source modules have colocated tests"
if not missing
else f"{len(missing)} module(s) without a colocated test"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"missing": missing[:20]},
fix_hint=(
f"Add colocated tests: {', '.join(missing[:5])}" if missing else None
),
)
|
NodeTestPyramidRule
Bases: ProjectRule
Colocated *.test.ts are unit tests; flag ones doing real I/O.
A colocated unit test that touches the filesystem/network/subprocess is a
soft signal it belongs in tests/integration or tests/e2e instead.
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| @register_rule("test_quality", framework=Framework.NODE)
class NodeTestPyramidRule(ProjectRule):
"""Colocated ``*.test.ts`` are unit tests; flag ones doing real I/O.
A colocated unit test that touches the filesystem/network/subprocess is a
soft signal it belongs in ``tests/integration`` or ``tests/e2e`` instead.
"""
@property
def rule_id(self) -> str:
"""Unique identifier (shared with the Python pyramid-level rule)."""
return "TEST_QUALITY_PYRAMID_LEVEL"
def check(self, project_path: Path) -> CheckResult:
"""Flag colocated unit tests that perform real I/O."""
src = _src_dir(project_path)
if src is None:
return _no_package_json(project_path, self.rule_id)
misplaced: list[str] = []
for test in self._colocated_tests(src):
text = test.read_text(encoding="utf-8", errors="replace")
if _REAL_IO.search(text):
misplaced.append(test.relative_to(src).as_posix())
score = max(0, 100 - len(misplaced) * 15)
passed = score >= PASS_THRESHOLD
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"Colocated tests are pure unit tests"
if not misplaced
else f"{len(misplaced)} colocated unit test(s) do real I/O"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"misplaced": misplaced[:20]},
fix_hint=(
"Move I/O tests to tests/integration or tests/e2e"
if misplaced
else None
),
)
@staticmethod
def _colocated_tests(src: Path) -> list[Path]:
"""List ``*.test.ts``/``*.spec.ts`` files colocated in the source tree."""
return sorted(p for p in _iter_files(src) if _is_test_file(p))
|
rule_id
property
Unique identifier (shared with the Python pyramid-level rule).
check(project_path)
Flag colocated unit tests that perform real I/O.
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| def check(self, project_path: Path) -> CheckResult:
"""Flag colocated unit tests that perform real I/O."""
src = _src_dir(project_path)
if src is None:
return _no_package_json(project_path, self.rule_id)
misplaced: list[str] = []
for test in self._colocated_tests(src):
text = test.read_text(encoding="utf-8", errors="replace")
if _REAL_IO.search(text):
misplaced.append(test.relative_to(src).as_posix())
score = max(0, 100 - len(misplaced) * 15)
passed = score >= PASS_THRESHOLD
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"Colocated tests are pure unit tests"
if not misplaced
else f"{len(misplaced)} colocated unit test(s) do real I/O"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"misplaced": misplaced[:20]},
fix_hint=(
"Move I/O tests to tests/integration or tests/e2e"
if misplaced
else None
),
)
|
NodeTestTautologyRule
Bases: ProjectRule
Flag tautological assertions (expect(true), x.toBe(x)).
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| @register_rule("test_quality", framework=Framework.NODE)
class NodeTestTautologyRule(ProjectRule):
"""Flag tautological assertions (``expect(true)``, ``x.toBe(x)``)."""
@property
def rule_id(self) -> str:
"""Unique identifier (shared with the Python tautology rule)."""
return "TEST_QUALITY_TAUTOLOGY"
def check(self, project_path: Path) -> CheckResult:
"""Count tautological assertions across the project's test files."""
if not (project_path / "package.json").is_file():
return _no_package_json(project_path, self.rule_id)
hits: list[str] = []
for test in _all_test_files(project_path):
text = test.read_text(encoding="utf-8", errors="replace")
for lineno, line in enumerate(text.splitlines(), start=1):
if any(pat.search(line) for pat in _TAUTOLOGY_PATTERNS):
rel = test.relative_to(project_path).as_posix()
hits.append(f"{rel}:{lineno}")
# Tautologies are zero-tolerance: any occurrence fails (like the
# Python tautology rule), score grades severity.
score = max(0, 100 - len(hits) * 10)
passed = not hits
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"No tautological assertions"
if not hits
else f"{len(hits)} tautological assertion(s)"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"tautologies": hits[:20]},
fix_hint="Replace weak asserts with behavioral ones" if hits else None,
)
|
rule_id
property
Unique identifier (shared with the Python tautology rule).
check(project_path)
Count tautological assertions across the project's test files.
Source code in packages/axm-audit/src/axm_audit/core/rules/node/test_quality.py
| Python |
|---|
| def check(self, project_path: Path) -> CheckResult:
"""Count tautological assertions across the project's test files."""
if not (project_path / "package.json").is_file():
return _no_package_json(project_path, self.rule_id)
hits: list[str] = []
for test in _all_test_files(project_path):
text = test.read_text(encoding="utf-8", errors="replace")
for lineno, line in enumerate(text.splitlines(), start=1):
if any(pat.search(line) for pat in _TAUTOLOGY_PATTERNS):
rel = test.relative_to(project_path).as_posix()
hits.append(f"{rel}:{lineno}")
# Tautologies are zero-tolerance: any occurrence fails (like the
# Python tautology rule), score grades severity.
score = max(0, 100 - len(hits) * 10)
passed = not hits
return CheckResult(
rule_id=self.rule_id,
passed=passed,
message=(
"No tautological assertions"
if not hits
else f"{len(hits)} tautological assertion(s)"
),
severity=Severity.WARNING if not passed else Severity.INFO,
score=score,
details={"tautologies": hits[:20]},
fix_hint="Replace weak asserts with behavioral ones" if hits else None,
)
|